<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>简言之</title>
  
  <subtitle>慢慢来，比较快！</subtitle>
  <link href="https://jwt1399.top/atom.xml" rel="self"/>
  
  <link href="https://jwt1399.top/"/>
  <updated>2025-11-22T04:04:43.238Z</updated>
  <id>https://jwt1399.top/</id>
  
  <author>
    <name>简简</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>人生中场，我有些话想说</title>
    <link href="https://jwt1399.top/posts/46990.html"/>
    <id>https://jwt1399.top/posts/46990.html</id>
    <published>2025-10-31T03:34:21.000Z</published>
    <updated>2025-11-22T04:04:43.238Z</updated>
    
    <content type="html"><![CDATA[<center><b>谨以此文献给正在工作或即将工作的你</b></center><p>工作后我频繁的听到这样一个观点：能进大厂的大家，都是通俗意义上的“聪明人”、“好学生”，大家在学校里或多或少都有一些自己的成就，相比于同龄的人你们已经足够优秀，你们已经跑在了前面。</p><p>但我想说：学校有一套普适的衡量标准，但这世上最难的事，从来不是有标准答案的事，而是那些没有答案的事。</p><p>“老板到底想要什么？”、“我怎么才能变得更好？”、“未来我会变成什么样？”、甚至“她为什么不喜欢我？”</p><p>出了学校，此后道路，再也不是每一步都可谋划的清清楚楚，再也不像做题一样，所有问题都能找到答案。</p><p>当然谁都无法一开始就能把每一步都想得那么清楚，就算武侯在世，也算不到三国终局。</p><p>我的观点是：<strong>理解的，就在理解中执行；不理解的，就在执行中理解。</strong></p><p>微软 CEO 「Satya Nadella」 在 「Garry Tan」 访谈中说过一段话</p><blockquote><p>并不是说一开始你就知道自己会走到什么位置，也不会从一开始就设定一个明确的终点。但你确实是带着目标上路的，瞄准第一的位置，并且对自己希望达成的事设定最高的期望。我总是说，我并不是等到成为 CEO 才开始努力工作的。实际上我做的第一份工作，那已经是我 1992 年加入这家公司时，所能拥有的最棒的工作了。当时我甚至觉得，如果我能从那份工作退休，那就太好了。现在回头看，那是一种非常棒的思维模式。重点不是等到下一次升职才去发力，而是利用手上的每一个机会，尽全力做好眼下的事情。今天刚刚起步的建设者、创始人、研究者、学生都可以带着这样的心态。保持热情，不要等待 “下一个大事件”，把你现在手里的事情当成最重要的，然后不断扩展它的影响。</p></blockquote><p>进入社会，当大家以「价值交换」作为行为准则的时候，大家<strong>不是在较量人格的上限，而是在法律的边缘试探道德的下限。</strong>「慈不掌兵，义不行賈」，出来做生意，永远是越没有道德的人越能攫取超额的利润。</p><p>「品学兼优」的优等生，走出校园，最初见识到人性中最丑陋的一面的时候，他们的三观很可能要崩溃一次，我也是在一次次崩溃中，学会成长！</p><p>我花了很多年才明白这个道理，晚了很久，以前我总是真诚的对待每一个人，但是林子大了什么人都有，各种无下限的事令我咋舌，现在真诚被我用来筛选同路人的手段。希望大家能够比我更早地明白，好走的快一点。</p><p>当下的我们卷不赢，又躺不平，为了保持干劲给自己设下一个又一个目标，却又总是因为自己所谓的不够“自律”而陷入自我否定、怀疑挣扎以至于摆烂一样的反抗。</p><p>正如「韩炳哲」老师在《倦怠社会》中写的那样</p><blockquote><p>无穷无尽的自我提升的需求，一种近乎歇斯底里的自我鞭策和自我督促、永不停歇的向上追赶的压力，让现代人几乎没有喘息的机会，因而远离平静、幸福、满足的生活状态。</p></blockquote><p>你是否也遇到职场倦怠？对我来说，职场倦怠其实是周期性的，特别容易出现在一个项目的收尾，而新项目没有头绪的时候。</p><p>管理学中四层管理：一阶管事、二阶管人、三阶管目标、四阶管动机。在我看来，这件事不只用到管理团队，也可以很契合地用到我们管理自己身上。我到底是如何管理自己的呢？</p><h4 id="1、沟通篇——温柔且坚定"><a href="#1、沟通篇——温柔且坚定" class="headerlink" title="1、沟通篇——温柔且坚定"></a>1、沟通篇——温柔且坚定</h4><p>有很多的人第一次见面就会觉得我是个很温柔的人，但随着深入接触后，我总被冠以批判性强，有自己强烈主观色彩，不是个好欺负的人，我开始意识到，我逐渐建立起了一种「温柔且坚定」的形象。</p><p>我的态度是友善的，观点是鲜明的，立场是坚定的，心态是包容的。</p><p>在这个快节奏的时代，每个人在工作去和别人沟通的时候，都免不了产生很多戾气。</p><p>“你怎么这么笨？”、“你明不明白我在说什么？”、“为什么我这么努力还要批评我？”、“我都说了多少遍了？”</p><p>从微观层面，<strong>你所有的愤怒，都源自于缺乏掌控的无力感。</strong>简言之，无能怒吼。</p><p>从宏观层面，<strong>你所有的愤怒，都源自于契约失序的背叛感。</strong>简言之，正义失声。</p><p>慢慢我开始觉得不懂得管理自己情绪的人，是<strong>不职业的，不成熟的</strong>，我在工作中已经很少有情绪波动了，当有人随意发泄情绪，我就会天然的在心里给这个人下评价——“这个人不成熟，情绪化，意气用事”，甚至有人发泄情绪时，我会阴阳怪气的去拱火，让 TA 进一步破防。像极了「你平静的把我逼疯，然后说我情绪不稳定」。</p><p>也有人说我患了情绪羞耻症，我承认，生而为人，情绪是我的一部分，但我习惯了<strong>压抑感性脑使用理性脑</strong>，也无意识带到了亲密关系中。</p><p>当我们试图剥离情绪去解读深层的根因，你就会发现，当你对现实的复杂性和不可逆性有足够的认知和预期，你就不会情绪失控，而是会接纳自己不可改变的部分，从行动上去改变自己可以改变的部分。</p><p>你应该知道：<strong>以解决问题为导向，情绪解决不了任何问题，而且会让解决问题更加困难。</strong></p><p>我理解「温柔」是一种能够理解别人的心情和诉求，不去苛责任何人，我们一起想办法解决的状态。当然温柔并不是无底线的讨好，背后应该有一个非常强大的认知框架在支撑，否则很难做到「坚定」。坚定是除非遇到影响关键决策的信息输入，否则我的目标和诉求不会改变，我的底线也不能退让。</p><p>我知道「温柔且坚定」是一个很难修炼到的状态，需要阅历和时间，我也承认我并非 100% 处于这个状态，但近半年我持续处于这个状态中，越来越多的人说：小简你越来越成熟了。</p><p>最后是我们无数次提醒自己的话，与您共勉</p><p>不再把自己置于别人的评价体系里，不再幻想周围的人都是我的观众，不再给自己上弦，更好的做自己。</p><p>请把讨好型人格的对象变成自己，敢于对别人说不，让自己不爽的懂得怼回去了而不是自己一个人生闷气，对别人的眼光别那么在意了，也能接受每个人的离开和失去了。舍得对自己花钱，学会满足自己的需求。减少对别人期待，懂得自己的底气只能是自己。减少无效社交，内心丰盈者独行亦如众，I am a one man army。</p><h4 id="2、工作篇——完美主义是种病，得治！"><a href="#2、工作篇——完美主义是种病，得治！" class="headerlink" title="2、工作篇——完美主义是种病，得治！"></a>2、工作篇——完美主义是种病，得治！</h4><p>我相信大部分人仍然以「快速完成任务」作为自己的评价标准，严重依赖「领导交代干什么」作为输入，往往不具备<strong>主动收集信息、独立发现问题、自驱解决问题的能力</strong>。</p><p>我们的教育体系，培养了太多只执行不思考，只被动接受不主动行动的工具人（当然我也不例外），甚至在他们的评价体系里，只有「干活」才是「产出」，所以他们天然鄙视「光开会不干活的」领导。别问我为什么知道，从我现在的角度，我认为还抱有这样思想的同学确实也难堪大任，除了当「螺丝钉」和「老黄牛」，也确实做不了别的事了。</p><p>解答一个很多人问过的问题，「琐碎的事太多，怎么办？」</p><p>毁掉一个人最好的方式，是用琐碎的事消耗 TA，让 TA 没精力思考未来。我确实不赞同，让新人一开始就干琐碎的事，因为这很难让TA 建立起体系化的思维，等到 TA 能分辨对错的时候，就可以开始承担一些琐碎的事了。</p><p>你在做琐碎的事情时，你应该去思考这些事为什么成为琐碎的事，是否可以通过技术减少琐碎的事</p><p>总之：<strong>复杂的事情简单化，简单的事情标准化，标准的事情流程化，流程的事情自动化</strong></p><p>回到主题，在商业社会，<strong>完美主义其实就是自我感动</strong>，这是你自己的需求，你出来工作拿钱，满足的是别人的需求，请务必记住。</p><p>「完美主义」对我来说就是一个非常一体两面的特质。它的正面，是我能将问题想得全面彻底，能规划出简洁优雅的系统性解决方案；它的反面，就是我经常在无关紧要的细节上纠结，会提前想到各种可能的负面后果而裹足不前。</p><p>解药是：干中学</p><p>当你纠结用哪种方案的时候，就先用一种成本最低的方案去实现；不知道行不行，那就试了再说。做起来了就有反馈，有了反馈就能持续优化，而这种反馈，是你在前期的纠结中无论如何也得不到的。</p><p>更何况，在内耗中你会设想几十上百种可能的负面情形，而真正做下来，你会发现90%的担忧实际上都没有发生。</p><h4 id="3、成长篇——培养输出型爱好和成长型爱好"><a href="#3、成长篇——培养输出型爱好和成长型爱好" class="headerlink" title="3、成长篇——培养输出型爱好和成长型爱好"></a>3、成长篇——培养输出型爱好和成长型爱好</h4><p>我把爱好归为三类：「输出型爱好、成长型爱好、输入型爱好」</p><p>「输出型爱好」，定义为「有明确利他属性产出物」的爱好。比如做手工，剪视频、写公众号，摄影，烹饪……</p><p>「成长型爱好」，定义为「需要持续练习以提升水平」的爱好。比如打羽毛球、玩魔方、攀岩、吉他、书法……</p><p>「输入型爱好」，定义为「既无利他属性也无需练习」的爱好。比如看书，追剧，打游戏，刷短视频，逛街……</p><p>我这里建议你在今后的业余时间，优先选择「输出型爱好」和「成长型爱好」，理由如下：</p><ul><li><p><strong>「输出型爱好」的「利他属性」，让你具备离开平台独立生存的能力。</strong>最近这个就业大环境大家也清楚，每个在大厂的人容易被这套体系螺丝钉化。当一个被规训得很好的螺丝钉被迫离开一家大厂，TA 第一时间想到的是「再找另一家大厂拧螺丝」，而不是「我通过自己的利他属性独立完成交付」。所有的「利他产出物」都具备潜在变现的可能性，而「爱好」能够让你持续投入精力去精进自我，专业能力自然会比要比「因为生存压力被迫学习技能」的人强很多。</p></li><li><p><strong>「成长型爱好」虽然不能直接变现，但能锻炼你的好奇心和专注度。</strong>其实我一直有一个观察：有一项取得很高成就的「成长型爱好」的人，做本职工作也都不会太差。究其原因，我认为如果在任何一项「成长型爱好」上做得很好的人，无一例外都经过了系统的学习和刻苦的练习，所以侧面反映了一个人的学习能力、达成目标过程中的专注度以及克服困难的毅力。更何况，有的成长型爱好的成果具备观赏性，也就具备了「利他属性」，或者在一个技能上有足够强的水平，也可以从事相关领域的教学工作。</p></li><li><p><strong>只有「输入型爱好」，你会被商业逻辑下的评价体系所绑架。</strong>根据我的实际体会，输入型爱好一般就是作为放松或者是打发无聊的方式，这样最严重的问题就是——他们只能在工作中获得价值感，因此也就很容易被工作中的评价体系所绑架。一旦工作不被认可，他们的世界就崩塌了。而事实上，工作的评价体系都是服务于商业逻辑的，而你是一个人，是一个鲜活个体，并没有必要被商业逻辑的标准去审视和评判，而你自己从来都没有通过其他的方式去获得价值感，也从来没有构建过自己的评价体系。当你能通过「输出型爱好」和「成长型爱好」获得正反馈，你就能认识到——工作，只是个谋生的手段，是生活的一部分而已。</p></li></ul><p>工作的倦怠终会到来，物质与钱的追求也终有尽时，人须要有一个自己信奉的“愿景”，人需要有能长久坚持的“希冀”。无论是对精致的追求、还是对技术的渴望，还是仅仅是让父母骄傲、让人高看一眼，只要是一件值得自己骄傲的东西，能持续性地、长久地激励并取悦自己，就可以。</p>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;center&gt;&lt;b&gt;谨以此文献给正在工作或即将工作的你&lt;/b&gt;&lt;/center&gt;

&lt;p&gt;工作后我频繁的听到这样一个观点：能进大厂的大家，都是通俗意义上的“聪明人”、“好学生”，大家在学校里或多或少都有一些自己的成就，相比于同龄的人你们已经足够优秀，你们已经跑在了前面。&lt;/p&gt;</summary>
        
      
    
    
    
    <category term="Share" scheme="https://jwt1399.top/categories/Share/"/>
    
    
    <category term="总结" scheme="https://jwt1399.top/tags/%E6%80%BB%E7%BB%93/"/>
    
  </entry>
  
  <entry>
    <title>致我最最最美好的 28 岁</title>
    <link href="https://jwt1399.top/posts/jwt28.html"/>
    <id>https://jwt1399.top/posts/jwt28.html</id>
    <published>2025-08-24T11:42:36.000Z</published>
    <updated>2025-11-05T02:14:58.344Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>2025年的夏天，很长，很热，也很吵。蝉鸣像某种提醒——我将 28 岁了。</p></blockquote><p>我慢慢开始喜欢用镜头频繁的记录，试图抓住感知细碎幸福的能力。这个年龄很奇妙，不再莽撞，也未被世故裹挟，想要大步往前又想要原地踏步。像一条晃动的吊桥，前后两端都有人招手，而我只能小心走过去。</p><p>最近压力还挺大，六七八月过得很匆忙且措手不及，忙着感知幸福也忙着与感伤和解。毕业后来到上海开启我的独居生活，每天都思绪万千，被各种情绪、想法包裹着，好的坏的都有。</p><p>好像每年夏天总会重启一些事情，坐在工位上，本就脆弱的注意力，很容易被过去的回忆、将来的打算分散，此刻的我表达欲和输出欲比以往都强。</p><p>人生这本书我也将读到第 28 页，在这之前很长的一段时间里，我是很惧怕和不接受这个数字的，内心总觉得自己还停留在23、24。25～27 我总是陷入深深的自我厌恶里，跳不出责备自己的怪圈，经常性地突然讨厌自己，认为自己烂透了，破碎得要死。但好像从工作后，慢慢学会了放过自己。</p><p>最近越来越这样觉得，28 才是人生最好的时刻呀✨，是成年后的第一个十年，18 以为见山是山，见月是月，以为浓烈的追逐和固执的欢喜才能算爱；28 目光所及，皆是答案，原来所有的好，都不如刚刚好。</p><p>28 是精神和经济上的双重自由，是自给自足的满足，是拥有做选择的勇气和承担后果的魄力，是更通透更豁达更爱自己更具有主体性，也是更明白自己想要什么和正在做什么。“事业有成所，遇良人生儿育女”如果这些是普世意义上 28 岁应该拥有的成功圆满，那我应该还才刚刚开始。虽然没有世俗意义上的成功，也没能顺利完成这个年纪好像该有的人生进度。但回首以前的自己，在感情、事业中犹豫挣扎痛苦迷茫的我，我更喜欢现在的我自己。</p><p>在见识到巨大的草台班子和所有人或相似或不同的人生轨迹后，我开始释怀并把自己解脱出来，接纳自己、承认自己的优点、无所谓过往的不堪。阅历和精神、经济层面的储备会让人变得强大，起码现在的我可以在部分方面做到刀枪不入。我发觉自己感知幸福的能力变强了，总能在日常细碎的生活中感到愉悦、放松和精神的升华。比如买到好看的衣服、看到好看的风景、拍到好看的照片、学习做饭、点燃香薰静静阅读的时刻等等，我越来越能体会幸福了，会在某个当下突然感叹：活着真好啊，还有好多事要做、好多美好要体验。</p><p>写给28岁的自己，人生是用来体验的，不是用来演绎完美的。请接受自己身上灰暗的部分，原谅自己的迟钝和平庸，允许自己失败，允许自己偶尔短路。请不再执着于 18 岁时留下的遗憾，过去的已经过去，允许事与愿违。“往前看哦，不要回头，往前走哦，不要回头”——前面有光，你只是还不知道需要多走几步。过去那些写错的字、走错的路、爱错的人，都替你铺了今天的台阶。所有短暂的“失去”，都是命运在玩换家战术，它拆掉茅草屋的地基，只为给你腾出建造城堡的土地。都说机会是给有准备的人，但我觉得机会是给主动找到它的人！</p><p>小简，生日快乐！</p>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;2025年的夏天，很长，很热，也很吵。蝉鸣像某种提醒——我将 28</summary>
        
      
    
    
    
    <category term="Share" scheme="https://jwt1399.top/categories/Share/"/>
    
    
    <category term="总结" scheme="https://jwt1399.top/tags/%E6%80%BB%E7%BB%93/"/>
    
  </entry>
  
  <entry>
    <title>工作初体验</title>
    <link href="https://jwt1399.top/posts/62842.html"/>
    <id>https://jwt1399.top/posts/62842.html</id>
    <published>2025-07-03T09:33:28.000Z</published>
    <updated>2025-11-22T03:57:19.868Z</updated>
    
    <content type="html"><![CDATA[<center><h2>职场菜鸟的一些感悟</h2></center><h2 id="零、前言"><a href="#零、前言" class="headerlink" title="零、前言"></a>零、前言</h2><p><a href="https://img.jwt1399.top/img/202511221153088">https://img.jwt1399.top/img/202511221153088</a></p><p>七年前，懵懂的我怀着好奇与憧憬，离开小镇，前往成都求学。初到时，陌生的环境让我感觉什么都是新鲜的，我在这里纵情挥洒着我的青春，从本科到硕士，一路拼搏，一路成长。</p><p>七年后，骨子里不甘平凡的我，选择重新出发，去到一个熟悉又陌生的城市——上海！熟悉的是这里是中国最繁华的城市，这里霓虹璀璨，高楼林立，是许多有志青年的向往地。陌生的是这里的一切，默认的环境，陌生的人，陌生的街头巷尾。</p><p>未来，我想在这里找到属于我的答案！想在这里开始我的人生新阶段！想在这里寻找我逝去的青春！想在这座城市的每一个角落发现些被岁月沉淀的美好！</p><h2 id="一、初到上海"><a href="#一、初到上海" class="headerlink" title="一、初到上海"></a>一、初到上海</h2><p>来之前是忐忑的，紧张的，也是激动的，开心的。</p><p>前者是因为，虽然去过大大小小的公司参与过工作，但并没有长久工作的经验。<br>后者是因为，我喜欢一切新鲜的事物吧，陌生的环境对我来说是一种全新的挑战，我喜欢这样的挑战。</p><p>成都，在夏天遇见就在夏天告别吧！就这样，我在盛夏七月之初离开成都来到了上海！</p><p>紧张的心还未平复，我便迫不及待的想要去了解这座城市，2020 年”护网行动”我曾短暂在上海停留过，但那时太过匆匆，我并没有去细细品味这座城市。印象中的上海只与繁华联系，高楼林立的陆家嘴，灯红酒绿的外滩，是我对上海的初印象，但也仅仅是小时候在电视里见过。那天我从万国建筑群，一路向前漫步，吹着微微的江风，感受人群从我身边走过，一种熟悉的感觉涌上心头，七年前，初到成都的那个夏天有过，那是一种很矛盾的感觉，一方面，我极力想向身边的人证明，上海是多好的城市呀，多少人向往的地方啊，而我在这里，会一直在这里；另一方面，我好像跟这格格不入啊，我好像并不属于这里。没有归属感应该是每个人初来上海的感受。</p><p>或许当下远方的十里洋场并不属于我，但是我相信，只要我不停下脚步， 它会离我越来越近。</p><h2 id="二、工作初体验"><a href="#二、工作初体验" class="headerlink" title="二、工作初体验"></a>二、工作初体验</h2><p>来上海快一年啦，想来是应该写一写这段时间的感悟了，现在是一个工作后的傍晚，公司大楼在炎炎夏日中的微风中添了几丝柔和，我忽然意识到，时间竟如此匆匆，万千思绪涌上心头，备忘录是个好东西。</p><p>初进职场的我多少有些局促和紧张，陌生的环境让我做任何事都小心翼翼，每天要学的东西很多，我每天沉浸知识的海洋里，时间过得非常快。公司的工作氛围对于我来说相当的友好，同事都是理工科出身，逻辑清晰如公式，<br>真诚坦率似直尺，沟通极其顺畅。大家都在认真的做着自己的事，没有职场剧里的暗流与硝烟，只有键盘轻敲、算法低语和偶尔迸发的灵感火花。 Leader、Mentor和同事都很好，对大家的初印象就是，很普通好像也没什么特别的，现实告诉我不要以貌取人，大家真的一个比一个优秀，顶尖的学历，卓越的能力，丰富的阅历，多彩的生活。我看见凌晨一点的代码库，仍留着他们修改的注释；直到听见他们用流利的英语，在跨国会议里举重若轻；直到发现他们游走十余个国家、跳伞、蹦极、徒步、为公益项目熬夜的侧影。我时常会陷入深深自卑，觉得要学的东西很多，跟大家还有很大的差距。自卑像影子般追着我，提醒我：“你站在一群发光的星辰中间”。原来真正的优秀，从不炫耀锋芒，它只是安静地存在，等待你靠近、触碰、然后成为照亮自己的光。</p><p>但时间和我都在往前走，原来星星的光可以照亮尘埃，我慢慢的适应了这里的生活。</p><h3 id="2-1-我与Leader"><a href="#2-1-我与Leader" class="headerlink" title="2.1 我与Leader"></a>2.1 我与Leader</h3><p>起初跟 Leader 的交流很少，只有必须 One On One 的时候我们才会有沟通，刚开始也仅仅是汇报下我的工作内容和进展，他也从不吝啬赞美，给我很大鼓励。可渐渐我变得不开心了，工作的压力、成长的孤单、生活的遗憾，每一个似乎都能把我压垮，我开始慢慢跟 Leader 分享我的烦恼，他总说别那么大压力，给我讲述他的故事，他读博时的挣扎，毕业后的彷徨，家庭生活的重担，工作时的困惑。他说回首过去，那时的压力好像都不算什么，总能熬过去。我能感受到Leader在帮我缓解情绪，可我是个很奇怪的人，会因为一些小事情绪低沉，但很快又好起来，可能只是看到好看的风景或是听到喜欢的歌，又恢复成元气满满的小太阳模式，可我好烦这样的我，困在情绪里真的不要太折磨人，可是我没办法。。。不过跟 Leader 交流中我还是收获了很多，相比与 Leader 的压力，我的忧愁算什么呢。。。</p><p>一切都是我必须经历的吧，那些打不倒我的，终究会让我变得更强！</p><h3 id="2-2-我与Mentor"><a href="#2-2-我与Mentor" class="headerlink" title="2.2 我与Mentor"></a>2.2 我与Mentor</h3><p>Mentor 是我交流最多的人， 他是个极具人格魅力的人，话不多，做事极其沉稳。刚到上海的那段时间，我像一只迷失在森林中的小鸟，不知所措，是他给我指引了方向。每天都带着我和他的饭搭子一起吃饭，吃完饭会去散步，路上还会聊很多，大家都很友好，那些轻松愉快的时光，让我很快融入了这个充满活力的团队。他总爱在午休时递来一杯奶茶——三分糖，温热，杯壁凝着细密水珠。我们并肩坐在落地窗边，茶香漫开的间隙里，他教我拆解复杂算法，也拆解人生难题。”这个报错怎么改？”、”需求变更怎么处理？”、”未来该往哪走？”</p><p>我抛出一个个笨拙的问题，像把石子扔进深潭。而他从不皱眉，只用耐心的涟漪回应：”试试看这样？”、”或许可以换个角度”、”别急，我们一起理”。反正不管我问多蠢的问题他总是会耐心解答，从来没说过我 “你怎么这都不会呀” 这种话。跟着他一起工作就很有安全感，因为不管多难的问题，他总能给我一些想法，让我不再那么惧怕。以前遇到困难，我总是先抱怨一番，渐渐的我不再抱怨了，我知道不管再难的问题总能解决，只是时间问题。打心底里觉得他是一个厉害谦虚又低调的人，我何时才能像他一样从容呢，不过至少现在我有一个目标了。</p><p>奶茶杯底的珍珠，嚼碎时是甜的。就像他给的答案——不疾不徐，却总能让我尝到成长的滋味。</p><h3 id="2-3-我与同事"><a href="#2-3-我与同事" class="headerlink" title="2.3 我与同事"></a>2.3 我与同事</h3><p>刚来的那段时间，我与同事们交流并不多，作为职场新人，前期的大部分时间我都花在学习新知识、熟悉工作流程上，因此在工作层面上也少有交集。再加上性格偏内向，不善于主动打破沉默，我也沉浸在自己的世界里，那时，我的想法很简单，只要把Leader和Mentor安排的任务完成好就够了，于是我把全部精力都投入到工作中，不太愿意花时间与精力与外界接触，我把自己封闭起来了，丢掉和失去了很多重要东西，可失去就是失去了。随着时间推移，我开始意识到这样的状态并不健康。我丢掉的不只是与人交往的乐趣，还有许多可能的朋友、合作者，甚至是更丰富的职场体验。我不想一直这样下去，于是我尝试做出一些改变——比如主动邀请隔壁组的同事一起健身。虽然起初只是简单的一句“要不要一起去健身房？”，但这却成了我走出舒适圈的第一步。</p><p>随着工作的变化和项目的推进，我与同事之间的合作也越来越多。我发现，其实大家并没有我想的那么难以接近，反而都很友好、随和。在一次次的交流中，我逐渐了解到他们生活中的另一面，每个人的生活都多姿多彩，充满了热情与活力，这些正是我所向往的。我开始明白，职场不仅仅是完成任务的地方，它也是一个人际交往的平台，是拓展视野、丰富人生的重要一环。现在的我，不再只是埋头做事的“执行者”，而是更愿意打开自己，去倾听、去分享、去连接。每一次与同事的互动，都是对我认知边界的扩展。这段经历让我成长了很多，也让我更加珍惜眼前的人和事。我相信，未来的我会在工作与生活的平衡中走得更远，成为一个更完整、更丰盈的人。</p><p>最动人的代码，永远写在人与人的相遇里。</p><h3 id="2-4-我与自己"><a href="#2-4-我与自己" class="headerlink" title="2.4 我与自己"></a>2.4 我与自己</h3><p>24年夏天，我捧着毕业证书站在梧桐大道，看蝉鸣裹着热浪掀起对未来的无限想象；<br>24年秋天，台风撞碎魔都结界，暴雨打湿衬衫时才惊觉，成年人的坚持不过是风雨中挪动半步的妥协；<br>24年冬天，与最爱的人渐行渐远的那天，地铁口玻璃倒映着零下两度的晚风，把所有期许都冻成未发送的对话框。</p><p>回忆再美好也只是曾经，那个曾经独自迷茫而又不知所措的孩子，当下的我会拍一拍他的肩膀告诉他：“小简加油！前面等待你的是一段精彩的人生”。 </p><p>入职一年，掌声与质疑曾同时抵达：掌声的背后是凌晨一点调试 Bug 的孤独；每一次方案的确定，藏着无数次推倒重来的勇气。但所有失落与烦恼，终被黄浦江的晚风揉碎，化作晨跑时耳机里的鼓点，提醒我：你一定可以的！如今的我也能为别人撑一把伞，我参与了公司“袋鼠宝贝”和“乡村儿童操场”公益项目，目前帮助了 5 个生患绝症的孩子，参与修建了 4 块乡村操场，这条路我想我会越走越远。有人说 25～30 是最容易焦虑的年纪，成家立业的压力接踵而至，而我们既无资本，也缺底气，而我想说大胆做自己别犹豫，犹豫一次，就永远错过。。。</p><p>又一次回看那时的自己，我或许不会教他做任何事，只会默默的看着他坐在工位上，写下服务于万千用户的第一行代码，或许真正的亲自体验过，犯过错，并沉淀下不再犯这种错误的方法，才能真正称之为成长！</p><h2 id="三、感悟"><a href="#三、感悟" class="headerlink" title="三、感悟"></a>三、感悟</h2><p>以为自己很勇敢，徒步、攀石，站在高点看日出日落，赏星月。夜爬，最深处的黑暗与惊喜，心跳过速到平静，心奇的去发现植物的生长，小动物的欢快，眼前的风景，一个人内心挣扎。疲惫不堪时，只能告诉自己，前面还有路，继续走，继续努力。身边路过的人们，成群结队，偶遇的陌生人轻声的说，延着路，注意安全。登顶那时，看云海、风、雨、山雾、汗水、泪水、痛苦，全都集结成眼前的一切。</p><p>我很庆幸我在 20 多岁这个野蛮生长的年纪，做出了来大城市工作的决定，我是一个自驱力没有那么强的人，大城市的人和事都可以推着我走，强迫我快速成长。一个人来到上海工作的确很难，但是上海教会了我独立，居安思危，未雨绸缪，也让我认识了很多非常优秀且有思想的人。或许回到小县城我会过上非常安逸的生活，但是安逸的生活只会磨灭人的斗志，不是吗。</p><p>我一个人走了很远很远的路，翻过了一座又一座的山才抵达了这里，在抉择的路上偶有遗憾，但并不后悔！</p><p>写给五年后的小简！祝我们顶峰相见！！！</p><h1 id="❤️Sponsor"><a href="#❤️Sponsor" class="headerlink" title="❤️Sponsor"></a>❤️Sponsor</h1><p>您的支持是我不断前进的动力，如果您感觉本文对您有所帮助的话，可以考虑打赏一下本文，用以维持本博客的运营费用，拒绝白嫖，从你我做起！🥰🥰🥰</p><table>  <tbody>     <tr>         <td style="text-align:center;">支付宝</td>         <td style="text-align:center;">微信</td>     </tr>   <tr>    <td style="text-align:center;" ><img width="200" src="https://jwt1399.top/medias/reward/alipay.png"></td>          <td style="text-align:center;"><img width="200" src="https://jwt1399.top/medias/reward/sponor_wechat.png"></td>       </tr></tbody></table>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;center&gt;&lt;h2&gt;职场菜鸟的一些感悟&lt;/h2&gt;&lt;/center&gt;

&lt;h2 id=&quot;零、前言&quot;&gt;&lt;a href=&quot;#零、前言&quot; class=&quot;headerlink&quot; title=&quot;零、前言&quot;&gt;&lt;/a&gt;零、前言&lt;/h2&gt;&lt;p&gt;&lt;a</summary>
        
      
    
    
    
    <category term="Share" scheme="https://jwt1399.top/categories/Share/"/>
    
    
    <category term="总结" scheme="https://jwt1399.top/tags/%E6%80%BB%E7%BB%93/"/>
    
  </entry>
  
  <entry>
    <title>此刻地坛在我</title>
    <link href="https://jwt1399.top/posts/28881.html"/>
    <id>https://jwt1399.top/posts/28881.html</id>
    <published>2025-06-04T07:29:25.000Z</published>
    <updated>2025-11-22T03:50:10.463Z</updated>
    
    <content type="html"><![CDATA[<center><h1>阳光在身上流转，此刻地坛在我</h1></center><p>其实很难想象我再一次为读书而写下长篇文字，但我也并不惊讶，因为冥冥之中这是早晚要到来的事，拿起笔再次写下自己的所思所想。一开始时懒惰、厌恶去推敲、逃避让我痛苦的思考，后来慢慢的这件事就退出了我的生活。</p><p>“<strong>可我什么也没忘，但是有些事只适合收藏。不能说，也不能想，却又不能忘。</strong>”</p><p>铁生，我该拿什么回忆你，是满地金黄的落叶，还是成片的老松柏。</p><h2 id="一、就命运而言，休论公道"><a href="#一、就命运而言，休论公道" class="headerlink" title="一、就命运而言，休论公道"></a>一、就命运而言，休论公道</h2><pre class="line-numbers language-tex"><code class="language-tex">史铁生的一生：（1951～2010）1951 年：1月4日，出生于北京市，从小跟奶奶生活。1972 年：21岁，因患先天性脊椎裂，双腿瘫痪，自杀三次，被人救下。1977 年：26岁，母亲离开人世，遗憾一生。1981 年：30岁，双肾失灵，患有严重肾病。1982 年：31岁，开始写作生涯。1989 年：38岁，与陈希米结婚，从此妻子成为他的腿。1991 年：40岁，发表《我与地坛》。1998 年：47岁，尿毒症，靠透析维持生命，写下《病隙随笔》。2010 年：59岁，突发脑溢血去世，遗言：捐赠器官。<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>我很难想象，一个 21 岁、原本意气风发的青年，在最该展翅高飞、最该无所畏惧的年纪，却突然被命运残忍的折断“双腿”，将你按进轮椅。绝望如潮水般袭来，迷茫与痛苦在无数个日夜调戏你的倔强，虐待你的坚强，啃噬你的灵魂，嘲笑你的不甘。那种铺天盖地的绝望、那种被世界瞬间推远的无助与茫然，不是旁人能够真正感同身受的。</p><p>如何挣脱宿命的安排，如何在废墟里把自己重新打捞出来，是铁生每一天都必须回答的命题。</p><p>可你却说：“<strong>职业是生病，业余在写作</strong>”、“<strong>先别去死，再试着活一活看</strong>”</p><p>工作后，我生活在上海，但我频繁的往北京跑，心里有种说不清道不明的期待，一座是载了六朝起落的“帝都”，一座是汇纳万国风云的“魔都”。它们之间的途程，像一条埋在岁月里的隧道，走进去，就像要与百年前的光阴撞个满怀。那些沉淀在尘土里的故事，那些刻在砖瓦上的文明，都在暗处等着，等着被一双脚、一双眼，重新唤醒。</p><p>年少时当我第一次踏足两座城市时，我被上海的繁华吸引，被北京的破旧劝退，随着在北京待的次数多了，我对这座城有了独特的情感，这里有些东西是上海无可替代的。</p><p>在北京我会频繁去地坛公园，学着铁生观察地坛的断壁残垣， 树下的光影斑驳，往来的路人与草木。</p><p>在上海我会频繁去北外滩，我喜欢吹着黄浦江的晚风，漫步在岸边，我想这里是我在上海的“地坛”。</p><p>或许这就是铁生说的：“<strong>人有时候只想独自静静的待一会，悲伤也成享受。</strong>”</p><p>去北京的地坛公园是我自从翻开了这本书起一直想做的事情。</p><p>因为这本书真的给了低谷时的我太多太多力量，在我不被理解的时候、感到无力的时候、孤独的时候、感慨命运的时候……</p><h2 id="二、在破碎里重建自我"><a href="#二、在破碎里重建自我" class="headerlink" title="二、在破碎里重建自我"></a>二、在破碎里重建自我</h2><p>我和大多数读者有着一样的态度：太幸福的人，是读不懂这本书的。</p><p>看不懂《我与地坛》是特别好的事，真的别看懂，还有铁生的《病隙碎笔》也希望你不要看懂。这些书都是特效药，是要出大问题才开的处方药。而余华的《活着》更像是感冒药，没事吃吃不会怎么样，不一定要知道药的作用，或许他悄悄帮你抵御了一次小感冒。</p><p>就像成长一样，你并非看见大人的生活就早早的知道他们的不易和辛苦。文学小说滞后性很强，或许某天你走到街上就理解了书里的故事。</p><p>就像 18 岁读《围城》和 28 岁读《围城》感悟肯定不同。有些书不必强迫自己去看，去理解。有了一定的人生阅历再去读，也不迟的。</p><p>学生时期我曾试着翻开这本书，总被那些看似平淡无味的文字绊住，读不了几页便没了兴致，随意扔在一旁。直到经历了很多事情，心绪在辗转中慢慢沉淀，再重新拾起这本放了许久的书，竟读出了先前未曾体会的深意，也在字里行间觅得许多思考。</p><p>铁生，今天是 2025 年 6 月 4 日，我来地坛了，在盛夏，跟你笔下的地坛问声好！公园里是沉厚的古木香与命运淡淡的苦味。踏入园中，总是想到书中的那句，“<strong>在人口密集的城市里，有这样一个宁静的去处，像是上帝的苦心安排。</strong>”</p><p>铁生，从白昼到夜幕低垂，我静静坐着。园中人迹寥寥，唯有鸟鸣相伴。每当心绪低落，《我与地坛》便成为我的慰藉，书中是没有答案的，但当读完一些章节后，心情会无比舒畅，我被文字安慰，被文字拥抱。</p><p>铁生，今天是 2025 年 11 月 13 日，我来地坛了，在深秋，跟你笔下的地坛问声好！公园里是清纯的草木味与生命蓬勃的甜味。踏入园中，总是想到书中的那句，“<strong>在满园弥漫的沉静光芒中，一个人更容易看到时间，并看到自己的身影。</strong>”</p><p>铁生，曾不懂你为何总在地坛久坐，不懂你说“<strong>死是一件不必急于求成的事，死是一个必然会降临的节日</strong>”背后的深意。直到自己亲历失去与困境，才懂这份平静有多难得——那是无数个日夜在绝望中挣扎后，与生死达成的和解。</p><p>铁生，你不是不怕死， 而是在地坛的寂静里，看清了生的本质：苦难或许是宿命，但如何活着，是自己的选择。</p><p><strong>太阳，每时每刻都是夕阳，也都是旭日。当他熄灭着走下山去收尽苍凉残照之际，正是他在另一面燃烧着爬上山散布烈烈朝晖之时。</strong></p><h2 id="三、在地坛我删掉了所有愿望"><a href="#三、在地坛我删掉了所有愿望" class="headerlink" title="三、在地坛我删掉了所有愿望"></a>三、在地坛我删掉了所有愿望</h2><p>下班后，我又去了地坛。没有红墙金瓦，只有苍老的古树、游客络绎不绝的园子，和放声歌唱的大爷大妈们。我坐在长椅上，翻阅起了手中的《我与地坛》，恰好是那章《好运设计》。</p><p>铁生在书里玩了一个游戏：如果能为自己设计一生好运，该怎么选？健康、聪明、爱情……他一一勾选，却很快发现：如果把所有痛苦都提前剔除了，快乐不过是一杯没了气泡的糖水。</p><p>“<strong>没有痛苦和磨难，你就不能强烈地感受到幸福。</strong>”</p><p>此时的我，正在为十二月的手术和主 R 的新项目焦虑。手机备忘录里写满了愿望：“希望手术效果会很好啊”、“希望新项目能够完成的很完美啊”……但它们只让我更心虚，像把命运的舵盘完全交给了运气。而那个真正被命运剥夺了行走能力的人，现在好像就在这座园子里，他告诉我：我们设计不了好运，我们只能设计自己对生活的深情。</p><p>从地坛回来后，我删掉了那些愿望。不是放弃愿望，而是换了一种“许愿”的方式。我不再许愿“好的效果，完美的项目”，而是告诉自己：“愿我能享受接下来的每一天，享受每完成一个项目的踏实感。完成比完美更重要！”</p><p>我发现，当愿望从“向外界祈求一个结果”，变成“向内心承诺一种方向”，它就不再是压在心口的石头，而成了脚下的路。史铁生没有得到他“设计”的好运，他却写出了最幸运的文字。因为他把愿望，从“想要得到什么”，换成了“想要深爱什么”。所以，我现在依然会许愿，只是不再等着愿望“实现”来定义我是否幸运。</p><p>地坛的夕阳斜照在琉璃瓦上，几百年前这样，几百年后也会这样。而我的愿望，在宏大的时间尺度下不过一粒微尘。但我知道，当我把“求好运”的焦虑，换成“做事情”的专注时——我已经在书写属于自己的《好运设计》了。</p><h2 id="四、每个人心中都有一座地坛"><a href="#四、每个人心中都有一座地坛" class="headerlink" title="四、每个人心中都有一座地坛"></a>四、每个人心中都有一座地坛</h2><p>“<strong>古园寂静，你甚至能感到神明在傲慢地看着你，以风的穿流，以云的变幻，以野草和老树的轻响，以天高地远和时间的均匀与漫长……</strong>”</p><p>铁生在园中度过了人生最彷徨的岁月，这座荒芜但并不衰败的园子成了他的避难所。你说：“<strong>我已不在地坛，地坛在我</strong>”。如果苦难真能够开出花，那么你的花朵定已绽放于字里行间、地坛的树影下、北海的风中……以及每一个被你文字拯救的读者心田。</p><p>也许我们每个人都该有一座“地坛”，一个属于自己的精神栖息地。我在园中静静的走着，思考着我心中的“地坛”，这次看了北平的秋天，我想人们总是会反复爱上北平的秋天吧。就像老舍先生在《住的梦》中说“<strong>天堂是什么样子，我不晓得，但是从我的经验去判断，北平之秋便是天堂。</strong>” 我爱北平的秋天，但明年还可以去看江南的春天，金陵的夏天，哈尔滨的冬天，这样就是凑够美丽的四季了。</p><p>我想此后的漫长人生，我会无数次重返地坛。</p><p>《我与地坛》从不是一本需要急着读完的书，它需要等我们历经世事、心怀沉淀，再与它坦诚相对。</p><p>如今这个崇尚成功、人人被焦虑裹挟的时代，我们总忙着追逐世俗意义的圆满，却忘了生命本身的厚重——其实每个人都有自己的不足与缺陷，人生从来没有绝对的圆满，我们都需要找到属于自己的 “地坛”。或许是一处安放迷茫的角落，一个抚慰心灵的爱好，或是一份支撑前行的信念，让我们在喧器中慢下来，与自己对话、与生活和解。</p><p>铁生你走后荒园变得好不热闹啊。你看呐，无数脚印正覆在你曾轧过的车辙上。循着你车轮的痕迹，在老松柏的荫蔽下寻觅你的踪迹。愿未来，我与你，一同笑看。</p><p>最后，我希望你们永远读不懂这本书，毕竟命运会把每个人推到合适的位置。</p><h2 id="💗Sponsor"><a href="#💗Sponsor" class="headerlink" title="💗Sponsor"></a>💗Sponsor</h2><p>在算法把世界切成碎片的年代，能坚持写长文的人，大概都是“为爱发电”。如果你恰巧财力雄厚，感觉本文对你有所帮助的话，可以考虑打赏一下本文，用以维持本博客的运营费用，让这盏小灯还能亮在深夜的互联网上。</p><table>  <tbody>     <tr>         <td style="text-align:center;">支付宝</td>         <td style="text-align:center;">微信</td>     </tr>   <tr>    <td style="text-align:center;" ><img width="200" src="https://jwt1399.top/medias/reward/alipay.png"></td>          <td style="text-align:center;"><img width="200" src="https://jwt1399.top/medias/reward/sponor_wechat.png"></td>       </tr></tbody></table>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;center&gt;&lt;h1&gt;阳光在身上流转，此刻地坛在我&lt;/h1&gt;&lt;/center&gt;

&lt;p&gt;其实很难想象我再一次为读书而写下长篇文字，但我也并不惊讶，因为冥冥之中这是早晚要到来的事，拿起笔再次写下自己的所思所想。一开始时懒惰、厌恶去推敲、逃避让我痛苦的思考，后来慢慢的这件事就退出了</summary>
        
      
    
    
    
    <category term="Share" scheme="https://jwt1399.top/categories/Share/"/>
    
    
    <category term="总结" scheme="https://jwt1399.top/tags/%E6%80%BB%E7%BB%93/"/>
    
  </entry>
  
  <entry>
    <title>大数据-Flink</title>
    <link href="https://jwt1399.top/posts/49185.html"/>
    <id>https://jwt1399.top/posts/49185.html</id>
    <published>2024-09-07T15:57:36.000Z</published>
    <updated>2024-09-08T13:59:01.634Z</updated>
    
    <content type="html"><![CDATA[<p><strong>Apache Flink</strong> 是一个在有界数据流和无界数据流上进行有状态计算分布式处理引擎和框架，Flink 批流统一，同一套代码，可以跑流也可以跑批，同一个SQL，可以跑流也可以跑批，可连接到常用的存储系统，如kafka、hive、JDBC、HDFS、Redis</p><h2 id="DataStream-API"><a href="#DataStream-API" class="headerlink" title="DataStream API"></a>DataStream API</h2><h2 id="Table-API-amp-SQL"><a href="#Table-API-amp-SQL" class="headerlink" title="Table API &amp; SQL"></a>Table API &amp; SQL</h2><blockquote><p>在 Flink 中，Table API 和 SQL 可以看作联结在一起的一套 API，这套 API 的核心概念是一个可以用作 Query 输入和输出的表 Table。在我们程序中，输入数据可以定义成一张表，然后对这张表进行查询得到一张新的表，最后还可以定义一张用于输出的表，负责将处理结果写入到外部系统。</p></blockquote><p>我们可以看到，程序的整体处理流程与 DataStream API 非常相似，也可以分为读取数据源(Source)、转换(Transform)、输出数据(Sink)三部分。只不过这里的输入输出操作不需要额外定义，只需要将用于输入和输出的表 Table 定义出来，然后进行转换查询就可以了。</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// 创建执行环境 TableEnvironment</span>EnvironmentSettings settings <span class="token operator">=</span> EnvironmentSettings        <span class="token punctuation">.</span><span class="token function">newInstance</span><span class="token punctuation">(</span><span class="token punctuation">)</span>        <span class="token punctuation">.</span><span class="token function">inStreamingMode</span><span class="token punctuation">(</span><span class="token punctuation">)</span>        <span class="token punctuation">.</span><span class="token function">useBlinkPlanner</span><span class="token punctuation">(</span><span class="token punctuation">)</span>        <span class="token punctuation">.</span><span class="token function">build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>TableEnvironment tableEnv <span class="token operator">=</span> TableEnvironment<span class="token punctuation">.</span><span class="token function">create</span><span class="token punctuation">(</span>settings<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 创建输入表</span>String sourceSql <span class="token operator">=</span> <span class="token string">"CREATE TABLE datagen_table (\n"</span> <span class="token operator">+</span>        <span class="token string">"    word STRING,\n"</span> <span class="token operator">+</span>        <span class="token string">"    frequency int\n"</span> <span class="token operator">+</span>        <span class="token string">") WITH (\n"</span> <span class="token operator">+</span>        <span class="token string">"  'connector' = 'datagen',\n"</span> <span class="token operator">+</span>        <span class="token string">"  'rows-per-second' = '1',\n"</span> <span class="token operator">+</span>        <span class="token string">"  'fields.word.kind' = 'random',\n"</span> <span class="token operator">+</span>        <span class="token string">"  'fields.word.length' = '1',\n"</span> <span class="token operator">+</span>        <span class="token string">"  'fields.frequency.min' = '1',\n"</span> <span class="token operator">+</span>        <span class="token string">"  'fields.frequency.max' = '9'\n"</span> <span class="token operator">+</span>        <span class="token string">")"</span><span class="token punctuation">;</span>tableEnv<span class="token punctuation">.</span><span class="token function">executeSql</span><span class="token punctuation">(</span>sourceSql<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 创建输出表</span>String sinkSql <span class="token operator">=</span> <span class="token string">"CREATE TABLE print_table (\n"</span> <span class="token operator">+</span>        <span class="token string">"  word STRING,\n"</span> <span class="token operator">+</span>        <span class="token string">"  frequency INT\n"</span> <span class="token operator">+</span>        <span class="token string">") WITH (\n"</span> <span class="token operator">+</span>        <span class="token string">"  'connector' = 'print'\n"</span> <span class="token operator">+</span>        <span class="token string">")"</span><span class="token punctuation">;</span>tableEnv<span class="token punctuation">.</span><span class="token function">executeSql</span><span class="token punctuation">(</span>sinkSql<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 执行计算并输出</span>String sql <span class="token operator">=</span> <span class="token string">"INSERT INTO print_table\n"</span> <span class="token operator">+</span>        <span class="token string">"SELECT word, SUM(frequency) AS frequency\n"</span> <span class="token operator">+</span>        <span class="token string">"FROM datagen_table\n"</span> <span class="token operator">+</span>        <span class="token string">"GROUP BY word"</span><span class="token punctuation">;</span>tableEnv<span class="token punctuation">.</span><span class="token function">executeSql</span><span class="token punctuation">(</span>sql<span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>Flink SQL 是 Flink 实时计算 为简化计算模型，降低用户使用实时计算门槛而设计的一套符合标准 SQL 语义的开发语言。</p><p>Flink SQL 是面向用户的 API 层，在我们传统的流式计算领域，比如 Storm、Spark Streaming 都会提供一些 Function 或者 Datastream API，用户通过 Java 或 Scala 写业务逻辑，这种方式虽然灵活，但有一些不足，比如具备一定门槛且调优较难，随着版本的不断更新，API 也出现了很多不兼容的地方。</p><h1 id="📚参考资料"><a href="#📚参考资料" class="headerlink" title="📚参考资料"></a>📚参考资料</h1><ul><li><a href="https://confucianzuoyuan.github.io/flink-tutorial/book/chapter01-00-00-%E7%AC%AC%E4%B8%80%E7%AB%A0%EF%BC%8C%E6%9C%89%E7%8A%B6%E6%80%81%E7%9A%84%E6%B5%81%E5%BC%8F%E5%A4%84%E7%90%86%E7%AE%80%E4%BB%8B.html">尚硅谷Flink教程 (confucianzuoyuan.github.io)</a></li><li><a href="https://cloud.tencent.com/developer/article/1972189">（上）史上最全干货！Flink SQL 成神之路</a></li><li><a href="https://flink.godaai.org/ch-table-sql/table-overview.html">8.1. Table API &amp; SQL 综述 — Flink 原理与实践 (godaai.org)</a></li></ul><h1 id="❤️Sponsor"><a href="#❤️Sponsor" class="headerlink" title="❤️Sponsor"></a>❤️Sponsor</h1><p>您的支持是我不断前进的动力，如果您感觉本文对您有所帮助的话，可以考虑打赏一下本文，用以维持本博客的运营费用，拒绝白嫖，从你我做起！🥰🥰🥰</p><table>  <tbody>     <tr>         <td style="text-align:center;">支付宝</td>         <td style="text-align:center;">微信</td>     </tr>   <tr>    <td style="text-align:center;" ><img width="200" src="https://jwt1399.top/medias/reward/alipay.png"></td>          <td style="text-align:center;"><img width="200" src="https://jwt1399.top/medias/reward/sponor_wechat.png"></td>       </tr></tbody></table>ink]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;p&gt;&lt;strong&gt;Apache Flink&lt;/strong&gt; 是一个在有界数据流和无界数据流上进行有状态计算分布式处理引擎和框架，Flink</summary>
        
      
    
    
    
    <category term="Data" scheme="https://jwt1399.top/categories/Data/"/>
    
    
    <category term="Flink" scheme="https://jwt1399.top/tags/Flink/"/>
    
  </entry>
  
  <entry>
    <title>SSO-单点登录</title>
    <link href="https://jwt1399.top/posts/49798.html"/>
    <id>https://jwt1399.top/posts/49798.html</id>
    <published>2024-08-16T15:48:58.000Z</published>
    <updated>2024-08-25T09:11:58.068Z</updated>
    
    <content type="html"><![CDATA[<p>随着企业的发展，完成与工作相关的内容通常需要涉及到若干个系统服务（例如内部聊天用Zoom，文件共享用Dropbox，办公用Office 365，还有员工信息管理系统等等），用户在操作不同的系统时，<strong>需要多次登录</strong>，很麻烦，我们需要一种全新的登录方式来实现多系统应用群的登录，这就是<strong>SSO-单点登录</strong>。</p><p><strong>用户只需要登录一次就可以访问所有相互信任的应用系统</strong>，即<strong>一次登录，到处使用，同样一处退出，处处退出</strong>。</p><blockquote><p>以游乐场的通票为例：我们只要买一次通票，就可以玩所有游乐场内的设施，而不需要在过山车或者摩天轮那里重新买一次票。在这里，买票就相当于登录认证，游乐场就相当于使用一套 SSO 的公司，各种游乐设施就相当于公司的各个产品。</p></blockquote><h2 id="SSO-类型"><a href="#SSO-类型" class="headerlink" title="SSO 类型"></a>SSO 类型</h2><p>SSO 解决方案使用不同的标准和协议来对用户凭证进行验证和身份验证。</p><ul><li><strong>安全访问标记语言 (</strong> <a href="https://www.okta.com/blog/2020/09/what-is-saml/"><strong>SAML</strong></a> <strong>)：</strong>SAML 是一种开放标准，SAML 使用 XML（一种浏览器友好的标记语言）来交换用户标识数据。基于 SAML 的 SSO 服务提供更好的安全性和灵活性，因为应用程序不需要在其系统上存储用户凭证。SAML 2.0 专门针对 Web 应用程序进行了优化，允许通过 Web 浏览器传输信息。</li><li><strong>开放授权 (OAuth)：</strong><a href="https://developer.okta.com/blog/2017/06/21/what-the-heck-is-oauth">OAuth</a>是一种开放标准授权协议，它允许应用程序安全地从其他网站获取用户信息，而无需提供密码。应用程序不是请求用户密码，而是使用 OAuth 来获得用户访问受密码保护的数据的权限。OAuth 通过 API 建立应用程序之间的信任，允许应用程序在已建立的框架中发送和响应身份验证请求。</li><li><strong>OpenID Connect (OIDC)：</strong><a href="https://www.okta.com/openid-connect/">OIDC</a>位于 OAuth 2.0 之上，用于添加有关用户的信息并启用 SSO 流程。它允许一个登录会话在多个应用程序中使用。例如，OIDC允许用户使用他们的 Facebook 或 Google 帐户登录服务，而无需输入用户凭据。</li><li><strong>Kerberos：</strong>Kerberos 是一种支持相互身份验证的协议，用户和服务器均可通过不安全的网络连接验证对方的身份。它使用票证授予服务颁发令牌来验证用户和软件应用程序（如电子邮件客户端或 wiki 服务器）。</li><li><strong>智能卡身份验证：</strong>除了传统的 SSO，硬件也可以实现相同的过程，例如用户插入计算机的物理智能卡设备。计算机上的软件与智能卡上的加密密钥交互以对每个用户进行身份验证。虽然智能卡非常安全并且需要 PIN 才能操作，但它们必须由用户随身携带，存在丢失的风险。它们的操作成本也可能很高</li></ul><h2 id="CAS"><a href="#CAS" class="headerlink" title="CAS"></a>CAS</h2><p>**CAS（Central Authentication Service） **—— 中央认证服务，是 <strong>Yale</strong> 大学发起的一个企业级的、开源的项目，旨在为 <strong>Web 应用</strong>系统提供一种可靠的 <strong>SSO</strong> 解决方案。</p><p>CAS 官网：<a href="https://www.apereo.org/programs/software/cas">CAS | Apereo Foundation</a></p><p>CAS 源码：<a href="https://github.com/apereo/java-cas-client">apereo&#x2F;java-cas-client: Apereo Java CAS Client (github.com)</a></p><h3 id="CAS-组成"><a href="#CAS-组成" class="headerlink" title="CAS 组成"></a>CAS 组成</h3><ul><li><p>CAS Server：CAS Server 负责完成对用户的认证工作 , 需要独立部署 。 CAS Server 会处理用户名 &#x2F; 密码等凭证(Credentials)。</p></li><li><p>CAS Client：负责处理对客户端受保护资源的访问请求，需要对请求方进行身份认证时，重定向到 CAS Server 进行认证。（CAS Client与受保护的客户端应用部署在一起，以 Filter方式保护受保护的资源）</p></li></ul><h3 id="CAS-票据"><a href="#CAS-票据" class="headerlink" title="CAS 票据"></a>CAS 票据</h3><blockquote><ul><li><p>用户在 CAS 认证成功后，CAS 生成 Cookie （TGC），写入浏览器；</p></li><li><p>同时生成一个 TGT 对象，放入自己的缓存（Session），TGT 对象的 key 就是Cookie（TGC）的值；</p></li><li><p>当 HTTPS 再次请求到来时，如果传过来的有 CAS 生成的Cookie，则 CAS 以此 Cookie 值为 key 查询缓存中有 无TGT ，如果有的话，则说明用户之前登录过，如果没有，则用户需要重新登录。通过TGT 来获取 ST(Service Ticket)，通过ST来访问具体服务。</p></li></ul></blockquote><p><img src="https://img.jwt1399.top/img/202408182124306.png"></p><ul><li><p><strong>TGT</strong>（Ticket Grangting Ticket） ：CAS Server 创建 <strong>TGT</strong>，存在 <strong>CAS Server</strong> 的 <strong>Session</strong> 里面，根据用户信息签发的，拥有了TGT，用户就可以证明自己在CAS成功登录过。</p></li><li><p><strong>TGC</strong>（Ticket Granting Cookie）： 创建 <strong>TGT</strong> 的同时，生成 <strong>TGC</strong>。通过 <strong>CAS Server</strong> 的 <strong>response header</strong> 的 <strong>set-cookie</strong> 字段设置 TGC</p><ul><li>由 CAS Server 通过 SSL 方式发送给终端用户，存放用户身份认证凭证的Cookie，在浏览器和CAS Server间通讯时使用，并且只能基于安全通道传输（Https），是CAS Server用来明确用户身份的凭证。</li></ul></li><li><p><strong>ST</strong>（Service Ticket）： 根据 <strong>TGT</strong> 签发的 <strong>ST</strong>，是 <strong>CAS</strong> 为用户签发的访问某一服务的票据（SessionId）</p><ul><li>用户访问service时，service发现用户没有ST，则要求用户去CAS获取ST。用户向CAS发出获取ST的请求，如果用户的请求中包含Cookie（TGC），则CAS会以此Cookie值为key查询缓存中有无TGT，如果存在TGT，则用此TGT签发一个ST，返回给用户。用户凭借ST去访问service，service拿ST去CAS验证，验证通过后，允许用户访问资源。</li></ul></li></ul><h2 id="SSO-原理"><a href="#SSO-原理" class="headerlink" title="SSO 原理"></a>SSO 原理</h2><h3 id="SSO-登录"><a href="#SSO-登录" class="headerlink" title="SSO-登录"></a>SSO-登录</h3><p><img src="https://fhfirehuo.github.io/Attacking-Java-Rookie/image/c15-cas1-1.png" alt="login"></p><p>上面是一张SSO登录原理图，下面我们来分析一下具体的流程：</p><ol><li>首先用户访问系统1受保护的资源，系统1发现未登陆，跳转至SSO认证中心，并将自己的参数传递过去</li><li>SSO认证中心发现用户未登录，将用户引导至登录页面</li><li>用户输入用户名和密码提交至SSO认证中心</li><li>SSO认证中心校验用户信息，创建用户与SSO认证中心之间的会话，称为全局会话，同时创建授权令牌</li><li>SSO认证中心带着令牌跳转会最初的请求地址（系统1）</li><li>系统1拿到令牌，去SSO认证中心校验令牌是否有效</li><li>SSO认证中心校验令牌，返回有效，注册系统1的地址</li><li>系统1使用该令牌创建与用户的会话，称为局部会话，返回给用户受保护资源</li><li>用户访问系统2受保护的资源</li><li>系统2发现用户未登录，跳转至SSO认证中心，并将自己的地址作为参数传递过去</li><li>SSO认证中心发现用户已登录，跳转回系统2的地址，并附上令牌</li><li>系统2拿到令牌，去SSO认证中心校验令牌是否有效</li><li>SSO认证中心校验令牌，返回有效，注册系统2地址</li><li>系统2使用该令牌创建与用户的局部会话，返回给用户受保护资源</li></ol><p>用户登录成功之后，会与SSO认证中心及各个子系统建立会话，用户与SSO认证中心建立的会话称为全局会话，用户与各个子系统建立的会话称为局部会话，局部会话建立之后，用户访问子系统受保护资源将不再通过SSO认证中心，全局会话与局部会话有如下约束关系：</p><ul><li>局部会话存在，全局会话一定存在</li><li>全局会话存在，局部会话不一定存在</li><li>全局会话销毁，局部会话必须销毁</li></ul><h3 id="SSO-注销"><a href="#SSO-注销" class="headerlink" title="SSO-注销"></a>SSO-注销</h3><p>既然有登陆那么就自然有注销，单点登录也要单点注销，在一个子系统中注销，所有子系统的会话都将被销毁。原理图如下：</p><p><img src="https://fhfirehuo.github.io/Attacking-Java-Rookie/image/c15-cas1-2.png" alt="logout"></p><p>SSO认证中心一直监听全局会话的状态，一旦全局会话销毁，监听器将通知所有注册系统执行注销操作</p><p>同样的我们也来分析一下具体的流程：</p><ol><li>用户向系统1发起注销请求</li><li>系统1根据用户与系统1建立的会话id拿到令牌，向SSO认证中心发起注销请求</li><li>SSO认证中心校验令牌有效，销毁全局会话，同时取出所有用此令牌注册的系统地址</li><li>SSO认证中心向所有注册系统发起注销请求</li><li>各注册系统接收SSO认证中心的注销请求，销毁局部会话</li><li>SSO认证中心引导用户至登录页面</li></ol><h2 id="SSO-安全风险"><a href="#SSO-安全风险" class="headerlink" title="SSO 安全风险"></a>SSO 安全风险</h2><p>虽然单点登录为用户带来了便利，但它也给企业安全带来了风险。控制了用户 SSO 凭证的攻击者将获得访问用户有权访问的所有应用程序的权限，这增加了潜在损害的程度。</p><p>保证单点登录安全性的方式：</p><ol><li>安全协议和标准：使用安全协议和标准，如OAuth 2.0、OpenID Connect和SAML等，这些协议提供了身份认证、令牌管理和令牌传输的安全机制。</li><li>强密码策略：要求用户使用强密码，并实施密码策略，例如密码长度、复杂性要求、定期更改密码等。</li><li>双因素认证：引入双因素认证，结合密码和其他因素（如短信验证码、指纹识别等）进行身份验证，提高安全性。企业可以使用双因素身份验证 ( <a href="https://www.techtarget.com/searchsecurity/definition/two-factor-authentication">2FA</a> ) 或<a href="https://www.techtarget.com/searchsecurity/definition/multifactor-authentication-MFA">多因素身份验证</a>与 SSO 来提高安全性。</li><li>令牌的安全传输和存储：确保令牌在传输过程中进行加密，并在存储时进行安全保存，防止令牌泄漏或篡改。</li><li>会话管理：实施严格的会话管理机制，包括会话过期时间、会话注销和强制重新认证等，以减少会话劫持和会话固定攻击。</li><li>防止跨站脚本攻击（XSS）：采用适当的输入验证和输出编码，以防止XSS攻击，确保用户提交的数据不包含恶意脚本。</li><li>安全审计和监控：实施日志记录、监控和审计机制，及时检测并响应异常活动和安全事件。(踢蹬，溯源)</li></ol><p>常见的安全风险：</p><ol><li>令牌泄漏：令牌在传输或存储过程中被未授权的人员获取，导致身份被冒用。</li><li>会话劫持：攻击者获取有效会话的控制权，从而冒充合法用户进行操作。</li><li>跨站脚本攻击（XSS）：攻击者通过注入恶意脚本，从用户浏览器中窃取会话令牌或其他敏感信息。</li><li>令牌篡改：攻击者在传输过程中修改令牌的内容，以获取额外权限或冒充其他用户。</li><li>错误配置和漏洞利用：不正确的配置或漏洞导致攻击者绕过认证流程或访问未授权的资源。</li><li>社会工程学攻击：攻击者通过欺骗用户获取其凭据或其他敏感信息，例如钓鱼攻击、假冒网站等。</li></ol><h2 id="SSO-安全审计"><a href="#SSO-安全审计" class="headerlink" title="SSO 安全审计"></a>SSO 安全审计</h2><p>方案简述：消费边界日志（NIDS）和SSO登录日志，从「报文」和「行为」上计算特征（比如UA异常、异地登录&#x2F;访问是从报文的UA和IP来计算；登录没有权限的系统、短时间登录大量之前没有登录过的系统是从行为上计算），然后通过特征组合形成规则，命中规则后执行踢Token和告警。</p><p><strong>审计链路</strong></p><p>-&gt; 从NIDS日志中筛选SSO域名（sso.xxx.com）流量，从ResponseHeader、ResponseBody、Cookie中查找TGCX，并解析TGT结构体，同时关联ES中的历史登录日志，找出生成此TGT的IP，UA和登录接口（action）。</p><p>-&gt; 从SSO登录日志离线数据(ES)中提取用户过去历史访问过的应用集合，为用户历史应用访问行为基线（如xx时间内访问了xx个新应用）提供数据。</p><p>-&gt; 消费SSO登录Kafka日志，进行SSO单点登录审计（TGT盗用审计），主要逻辑：</p><ul><li><p>SSO登录日志写入Mysql，为审计任务提供历史数据（小时级别的），比如同一个TGT在前后不同的IP下使用，要对比其前后设备ID。注：之所以不直接使用ES的数据是因为ES查询稳定性和速度相比Mysql要差，Mysql的数据只存储1天。</p></li><li><p>逐条审计SSO登录日志，检查是否违反：</p><ul><li><p>a.TGT生成和TGT使用的环境信息不一致 </p></li><li><p>b. TGT最近两次使用的环境信息不一致</p></li><li><p>c.出现行为异常（如访问非目标岗位应用，没有IAM权限…）</p></li></ul></li></ul><p>（如果上面审计捕获到任意异常）则 </p><ul><li><p>a.将异常登录日志写入Mysql </p></li><li><p>b.过单个日志异常规则，判断是否是违反某个盗用规则</p></li><li><p>c.根据同一个IP、dfpid(设备唯一 id)回溯历史异常登录日志，按滑动窗口计算是否满足异常聚集阈值</p></li><li><p>d. 如果b&#x2F;c有命中，则下发票据处置决策（如果开启自动下发决策的话）</p></li></ul><p>当识别到一个票据命中盗用规则，或者产生异常特征聚集，则判断该票据被盗用，会通知SSO对票据做过期处置。</p><p>在实际场景中需要考虑误报对用户产生的干扰，特别是要防止短时间大量误报伤害大量用户体验，因此要考虑熔断。</p><h2 id="金银票据传递攻击"><a href="#金银票据传递攻击" class="headerlink" title="金银票据传递攻击"></a>金银票据传递攻击</h2><h3 id="Kerberos协议"><a href="#Kerberos协议" class="headerlink" title="Kerberos协议"></a>Kerberos协议</h3><p>Kerberos是一种计算机的认证协议，在域中使用kerberos作为认证手段。他提供了一种单点登录（SSO）的方法，比如打印服务器、邮件服务器和文件服务器。这些服务器都有认证的需求，很自然的，不可能让每个服务器自己实现一套认证系统，而是提供一个中心认证服务器（CAS）供这些服务器使用，这样任何客户端就只需要维护一个密码就能登录所有服务器。</p><p>角色：客户端（Client）、身份认证服务（AS）、票据授予服务（TGS）、普通服务器（Server）</p><p>Kerberos协议的基本流程是Client先通过AS（身份认证服务）进行身份认证，认证通过后AS会给Client一个TGT（票据授权票），Client将获取到的TGT发送到TGS（票据授予服务）进行身份验证，验证通过后TGS会给Client一个ST（服务票据），Client通过这个ST去访问指定Server上的服务。</p><img src="https://img.jwt1399.top/img/202408251018050.png" style="zoom: 33%;" /><h3 id="金银票据攻击"><a href="#金银票据攻击" class="headerlink" title="金银票据攻击"></a>金银票据攻击</h3><p>黄金票据、白银票据攻击是基于kerberos认证协议的攻击方式，常用来做后渗透域控权限维持。由于用户正常访问资源是也会请求服务票证，因此黄金、白银票据攻击具备很高的隐蔽性。</p><p>在kerberos认证中，主要解决两个问题</p><p>第一个问题：如何证明你本人是XXX用户的问题。（身份问题，黄金票据所在的问题，AS负责）</p><p>第二个问题：提供服务的服务器如何知道你有权限访问它提供的服务。当一个Client去访问Server服务器上的服务时，Server如何判断Client是否有权限来访问自己主机上的服务。（白银票据所在的问题，TGS负责）</p><h3 id="伪造黄金票据"><a href="#伪造黄金票据" class="headerlink" title="伪造黄金票据"></a>伪造黄金票据</h3><p>在kerberos认证中，Client通过AS认证后，AS会给Client一个TGT，而TGT是通过域控服务器上krbtgt账户的Hash进行加密的，所以只有得到krbtgt的Hash，就可以伪造TGT来进入下一步Client与TGS的交互。而在有了黄金票据后，就跳过AS验证，不用验证账户和密码，所以也不担心域管来修改密码。意思就是，当攻击者能够获得krbtgt的Hash后，攻击者就可以伪造一张票据授权票，去伪造成域内的任意用户访问域内kerberos认证的所有服务。</p><img src="https://img.jwt1399.top/img/202408251026591.png" style="zoom: 33%;" /><h3 id="伪造白银票据"><a href="#伪造白银票据" class="headerlink" title="伪造白银票据"></a>伪造白银票据</h3><p>黄金票据攻击是伪造TGT，白银票据攻击是伪造ST，在kerberos认证协议的第三步，Client带着ST向Server上的某个服务进行请求，Server接收Client的请求，验证通过后允许Client使用Server上的指定服务。所以只要有了服务器管理员账户的Hash，就能跳过向TGS请求ST的过程，可以直接伪造ST使用Server上的服务。</p><img src="https://img.jwt1399.top/img/202408251028999.png" style="zoom:33%;" /><h3 id="金银票据区别"><a href="#金银票据区别" class="headerlink" title="金银票据区别"></a>金银票据区别</h3><p>1.访问权限不同</p><p>黄金票据：伪造票据授权票（TGT），可以获取任何kerberos服务权限。</p><p>白银票据：伪造服务票据，只能访问指定的服务。</p><p>2.加密方式不同</p><p>黄金票据：由krbtgt的NTLM Hash加密。</p><p>白银票据：由服务账号（通常为计算机账户）的NTLM Hash加密。</p><p>3.认证流程不同</p><p>黄金票据：利用过程需要访问域控。</p><p>白银票据：不需要访问域控。</p><h1 id="📚参考资料"><a href="#📚参考资料" class="headerlink" title="📚参考资料"></a>📚参考资料</h1><ul><li><a href="https://mp.weixin.qq.com/s/ZeRtcloxrNC6wtozFkCkEQ">聊聊单点登录(SSO)中的CAS认证 (qq.com)</a></li><li><a href="https://cloud.tencent.com/developer/article/1921741">一文读懂认证、授权和SSO，顺便了解一下IAM-腾讯云开发者社区-腾讯云 (tencent.com)</a></li><li><a href="https://blog.csdn.net/qq_46110252/article/details/137470096">深入理解单点登录（SSO）：简化用户认证体验-CSDN博客</a></li><li><a href="https://blog.csdn.net/qq_33406875/article/details/90232656">https://blog.csdn.net/qq_33406875/article/details/90232656</a></li><li><a href="https://fhfirehuo.github.io/Attacking-Java-Rookie/Chapter15/cas1.html">https://fhfirehuo.github.io/Attacking-Java-Rookie/Chapter15/cas1.html</a></li><li><a href="https://juejin.cn/post/6945277725066133534">https://juejin.cn/post/6945277725066133534</a></li><li><a href="https://mp.weixin.qq.com/s/ZC5SFMgh8Zkitixp-aajUw">韭要学金银票据传递攻击(Pass The Ticket) (qq.com)</a></li></ul><h1 id="❤️Sponsor"><a href="#❤️Sponsor" class="headerlink" title="❤️Sponsor"></a>❤️Sponsor</h1><p>您的支持是我不断前进的动力，如果您感觉本文对您有所帮助的话，可以考虑打赏一下本文，用以维持本博客的运营费用，拒绝白嫖，从你我做起！🥰🥰🥰</p><table>  <tbody>     <tr>         <td style="text-align:center;">支付宝</td>         <td style="text-align:center;">微信</td>     </tr>   <tr>    <td style="text-align:center;" ><img width="200" src="https://jwt1399.top/medias/reward/alipay.png"></td>          <td style="text-align:center;"><img width="200" src="https://jwt1399.top/medias/reward/sponor_wechat.png"></td>       </tr></tbody></table>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;p&gt;随着企业的发展，完成与工作相关的内容通常需要涉及到若干个系统服务（例如内部聊天用Zoom，文件共享用Dropbox，办公用Office</summary>
        
      
    
    
    
    <category term="Data" scheme="https://jwt1399.top/categories/Data/"/>
    
    
    <category term="Security" scheme="https://jwt1399.top/tags/Security/"/>
    
  </entry>
  
  <entry>
    <title>数安-对象存储（更新中）</title>
    <link href="https://jwt1399.top/posts/54132.html"/>
    <id>https://jwt1399.top/posts/54132.html</id>
    <published>2024-08-02T17:00:35.000Z</published>
    <updated>2024-09-07T12:17:47.797Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>对象存储服务（Object Storage Service）是一种数据存储解决方案，将数据存储为对象，而不是传统的块存储或文件存储。对象存储系统具有高度的扩展性和灵活性，被广泛用于存储海量<strong>非结构化数据</strong>，如文档、图片、音视频文件等。然而，随着对象存储的普及，安全问题也变得愈发重要。</p></blockquote><h2 id="一、前言"><a href="#一、前言" class="headerlink" title="一、前言"></a>一、前言</h2><p>随着业务的发展，企业需要管理急剧增加并且孤立的大量数据，这些数据来自很多被任意数量的应用程序、业务流程和最终用户使用的来源。今天，这些数据中的大部分都是非结构化的，最终采用多种不同的格式和存储介质，并且不容易融入中央存储库。这增加了复杂性，并减慢了创新速度，因为无法访问数据以用于分析、机器学习（ML）或新的云原生应用程序。</p><p>对象存储能够提供可以大规模扩展并且经济高效的存储来以原生格式存储任何类型的数据，从而帮助企业打破这些限制。对象存储消除了困扰传统存储系统的复杂性、容量限制和成本障碍，因为对象存储以较低的每 GB 价格提供了无限的可扩展性。可以通过用户友好的应用程序界面集中管理非结构化数据。可以使用策略来优化数据存储成本，并在必要时自动切换存储层。云对象存储可以更轻松地执行分析和获得见解，从而加快决策速度。</p><p>数据作为企业的血液和命脉，其妥善保管至关重要。对象存储作为云计算的数据存储底座，并且还在支持数据湖存储能力，它是企业存储数据的理想之地。但是如何<strong>安全</strong>的存放数据到对象存储，尤其是公有云对象存储，常常让数据管理者感到困惑和疑虑。</p><p>本文将深入探讨一系列关键的安全议题，包括<strong>账户认证、网络配置、访问控制、数据加密、日志审计以及数据安全</strong>等方面，旨在全面解答<strong>企业将数据存放到对象存储</strong>过程中可能遇到的<strong>安全顾虑</strong>。</p><h2 id="二、厂商对象存储产品"><a href="#二、厂商对象存储产品" class="headerlink" title="二、厂商对象存储产品"></a>二、厂商对象存储产品</h2><ul><li><p>亚马逊对象存储 AWS S3（Simple Storage Service）</p></li><li><p>美团云对象存储 S3 Plus（Simple Storage Service Plus）</p></li><li><p>阿里云对象存储 OSS （Object Storage Service）</p></li><li><p>腾讯云对象存储 COS（Cloud Object Storage）</p></li><li><p>京东云对象存储 OSS（Object Storage Service）</p></li><li><p>百度云对象存储 BOS（Baidu Object Storage）</p></li><li><p>华为云对象存储 OBS（Object Storage Service）</p></li><li><p>又拍云对象存储 USS（ UPYUN Storage Service）</p></li><li><p>七牛云对象存储 Kodo</p></li><li><p>开源对象存储 MINIO</p></li></ul><h2 id="三、对象存储基础知识"><a href="#三、对象存储基础知识" class="headerlink" title="三、对象存储基础知识"></a>三、对象存储基础知识</h2><h3 id="1-常用术语"><a href="#1-常用术语" class="headerlink" title="1.常用术语"></a>1.常用术语</h3><table><thead><tr><th align="center">术语</th><th align="center">描述</th></tr></thead><tbody><tr><td align="center">OSS</td><td align="center">Object Storage Service，对象存储服务，简称为OSS</td></tr><tr><td align="center">Object</td><td align="center">OSS存储的单元，即OSS中存储的都是对象。</td></tr><tr><td align="center">Bucket</td><td align="center">用来存放对象的空间，也是对对象进行管理的空间。</td></tr><tr><td align="center">Region</td><td align="center">地区与终端节点，即云服务在不同Region有不同的访问域名。</td></tr><tr><td align="center">Endpoint</td><td align="center">OSS的访问域名，对象存储云化后，每个OSS服务都会形成一个URL，方便用户访问。</td></tr><tr><td align="center">AK&#x2F;SK</td><td align="center">访问密钥（Access Key ID&#x2F;Secret Access Key）包含访问密钥ID（AK）和秘密访问密钥（SK）两部分，是您的长期身份凭证，您可以通过访问密钥对API的请求进行签名。OSS通过AK识别访问用户的身份，通过SK对请求数据进行签名验证，用于确保请求的机密性、完整性和请求者身份的正确性。</td></tr></tbody></table><h3 id="2-AK-x2F-SK机制"><a href="#2-AK-x2F-SK机制" class="headerlink" title="2.AK&#x2F;SK机制"></a>2.AK&#x2F;SK机制</h3><p>AK&#x2F;SK 机制包含长期 AK、临时 AK(Security Token，ST)，每个用户可以生成多个  AK&#x2F;SK。通过 AK 识别访问用户的身份，通过SK对请求数据进行签名验证，用于确保请求的机密性、完整性和请求者身份的正确性。</p><ul><li>临时  AK&#x2F;SK 通过 expire 到期时间控制数据访问时效，提高安全性。</li><li>长期  AK&#x2F;SK一旦被盗取即获取长久访问权限，而 ST 在到期后要重新申请，所以实现安全加固。</li></ul><p>使用原理（签名，验签，非对称加密）</p><ul><li><p>客户端：拿到自己的AK&#x2F;SK之后，SK需要保密保存。</p><ul><li><p>1.使用原始报文进行hash算法计算得到摘要，然后使用SK对摘要进行加密生成签名（signature）；</p></li><li><p>2.发送HTTP请求给服务端[请求内容 &#x3D; Authorization(AK + expire_time  + signature)  + 原始报文]。</p></li></ul></li><li><p>服务端：</p><ul><li>1.根据客户端发送过来的AK，查找数据库得到对应的Ak和AK对应的用户信息；</li><li>2.使用上面步骤1相同的hash算法对原始报文进行计算得到摘要1，然后再用SK对摘要 1 签名得到一个签名2；</li><li>3.比较签名和签名2相同则认证通过，反之则不通过。</li></ul></li></ul><img src="https://img.jwt1399.top/img/202408170945469.png" style="zoom: 50%;" /><h2 id="四、对象存储安全审计"><a href="#四、对象存储安全审计" class="headerlink" title="四、对象存储安全审计"></a>四、对象存储安全审计</h2><h3 id="①账户认证"><a href="#①账户认证" class="headerlink" title="①账户认证"></a>①账户认证</h3><blockquote><p>存在问题：AK&#x2F;SK泄露&#x2F;盗用</p><p>关注问题：采用什么样的账号登陆？ 账号认证运行环境是否会被盗取<strong>？</strong></p></blockquote><p>企业数据上云，首先就是做好账户认证，即企业首先关注使用怎样的账号来登陆对象存储？保证账户能够正确的登陆对象存储，并且不会泄露AK&#x2F;SK。</p><ul><li>对于创业公司来说，很容易接受<strong>新账号体系</strong>；</li><li>对于成熟公司来说，已经建设了<strong>成熟的账号体系</strong>。</li></ul><p><strong>新账号体系</strong> </p><p>一套新的云账号体系，包含云账号、子用户、角色等，支撑企业人员灵活访问云资源。为了方便用于开发环境，即需要在不同的设备（云服务器、移动设备、IoT 等）访问数据，而对于不可信设备，记录账号的用户名和密码易导致泄漏，因此云服务提供了 <strong>AK&#x2F;SK机制</strong>，使用  AK&#x2F;SK 就可以方便的访问数据，而不需要通过账号密码。</p><p><strong>成熟账号体系</strong></p><ul><li><strong>已有 AD 账号通过 SSO 访问云資源</strong> 。为兼容已有账号（企业大量使用的 Active Directory 账号），可以通过 SSO 机制，将已有账号映射为用户 SSO、角色 SSO，从而得到长期 AK、临时 AK。</li><li><strong>已有 开放 ID 账号通过 OAuth 访问云資源</strong> 。为兼容已有 OpenID 账号，例如 WebApp、NativeApp、ServerApp的账号，可以通过 OAuth 机制，通过认证交换 Access Token，从而得到临时 AK。</li></ul><p><strong>使用临时 AK（STS Token）安全性更高</strong>。应用会部署在可信运行环境（独占使用的手机、服务器）和非可信运行环境（部署到第三方运行环境）</p><p><strong>采用长期 AK，会在 AK 泄漏时出现数据被盗取风险</strong>。因此，在非可信运行环境中，推荐采用临时 AK，它需要定期申请 STS Token 才能访问云資源，过期后将重新申请，从而提高安全性。</p><p><strong>非可信运行环境即使被攻破，也很难盗取 STS Token</strong>。黑客即使攻破服务器的操作系统，但STS Token是在应用程序里面动态获取，黑客需要破解实时运行的应用才能获取，难度非常大。</p><h3 id="②网络配置"><a href="#②网络配置" class="headerlink" title="②网络配置"></a>②网络配置</h3><blockquote><p>存在问题：内鬼转移企业数据、黑客盗取 AK</p><p>关注问题：公网访问能否限制来源地址和协议？ 云上VPC访问如何防止内鬼转移数据和外部黑客盗取数据？</p></blockquote><p>企业配置完成账号后，首要考虑的就是如何做好网络隔离，限制能够访问数据的地址和协议？</p><p>对象存储应提供如下功能：</p><ul><li><strong>指定数据只能被某些 IP 地址访问，通过 OSS 的 Bucket Policy 限制源地址范围</strong> 。不在允许源地址的公网请求，将被拒绝，将数据隔离在允许访问的网络 IP 地址范围内。</li><li><strong>指定数据只能采用允许的协议访问，通过 OSS 的 Bucket Policy 限制协议类型</strong> 。不在允许协议类型的公网请求，将被拒绝，从而应用只能用安全协议（https）来访问，提高网络传输安全性。</li></ul><p>企业内鬼可以同时拥有企业的AK和个人AK，从而可以将企业数据读出，然后写入到个人的桶資源。通过限制某VPC只能访问指定桶，从而内鬼在VPC内无法将数据写入其他桶（内鬼个人的桶） 。</p><p>黑客盗取企业AK后可以在控制机器盗取数据。通过限制企业桶只能被&lt;企业 VPC&gt;访问，黑客即使盗取企业AK，也无法在非企业 VPC 环境盗取数据，关键是配置 Bucket Policy 设置允许访问的 vpc-id。</p><h3 id="③访问控制"><a href="#③访问控制" class="headerlink" title="③访问控制"></a>③访问控制</h3><p>OSS提供读写权限ACL、授权策略、防盗链白名单等功能，实现存储资源访问的控制和管理。</p><blockquote><p>存在问题：公有桶遍历、公有桶爆破、私有桶签名未过期</p></blockquote><p>网络配置完成，就是让合理的账户具有合理的权限，此时就是做好授权访问。通过这三步，基本完成企业业务使用方的诉求。</p><p><strong>如何授权其他账号访问数据？</strong></p><p>常见对象存储 OSS 支持访问控制列表（ACL）和 授权策略（Policy）两种授权方式。 </p><ul><li><strong>ACL 包含 Bucket 级和 Object 级，按资源设置权限</strong> 。可以设置 ACL 为公共读写、公共读、私有、缺省 4 种类型。</li><li><strong>Policy 提供丰富的语义，可控制用户访问资源的路径和具体操作</strong> 。为某个用户 ID 可以访问那些资源，或者为某个桶可以被那些用户访问，使用哪些 API 来访问。</li><li><strong>除了ACL和Policy外，还提供防盗链为不同网站（或 IP 地址）的跨域访问设置权限</strong> 。通过白名单限制能够跨域访问的来源，实现数据共享。</li></ul><p><strong>如何授权匿名用户访问？</strong></p><p>通常应用都有把数据共享给公众的诉求， 因此如何授权匿名用户访问数据？</p><ul><li><strong>ACL 支持匿名访问设置</strong> 。可以设置桶级、对象级的公共读写、公共读，从而任何人都可以直接访问数据。</li><li><strong>签名URL</strong> 。为匿名用户生成签名 URL，并设置过期时间，匿名用户使用该签名URL可访问资源，从而实现指定时间范围内的数据共享。</li></ul><p>提供匿名访问能力，可能会带来数据泄漏、攻击植入、费用激增甚至欠费等问题，此时对象存储应提供一键停止公网访问能力，从而实现快速止血。</p><p><strong>如何让相同数据支持不同用户、不同权限的访问？</strong></p><p>对象存储支持多租户，典型是桶对应 1 个域名，通过 1 个 Bucket Policy 配置文件提供同一数据授权给不同用户不同权限访问。由于只有 1 个配置文件，每次修改某租户权限，运行生效都要加载整个文件，粒度过大。</p><p>为了解决该问题，<strong>对象存储应提供 Access Point（AP）实现细粒度用户权限</strong>。该功能可以为每个用户创建 AP，配置不同的 AP Policy，修改某用户的 AP Policy，对其他用户无影响。</p><p>Policy 都采用相同语法，助力实现灵活的多用户权限。通过操作、資源在一定条件下的权限，可实现丰富的用户权限管理。</p><p><strong>如何支持数据湖场景的细粒度授权？</strong></p><h3 id="④数据加密"><a href="#④数据加密" class="headerlink" title="④数据加密"></a>④数据加密</h3><p>提供服务器端加密和客户端加密，并支持基于SSL&#x2F;TLS的HTTPS加密传输，有效防止数据在云端的潜在安全风险。</p><p><strong>如何避免提供云服务的厂家访问到数据？</strong></p><ul><li><strong>客户不信任云厂家，选择客户端加密（CSE），数据上传前就已加密，无需担心云服务厂家访问到数据</strong> 。</li><li><strong>客户对云产品信任，可选择服务端加密</strong></li></ul><h3 id="⑤日志审计"><a href="#⑤日志审计" class="headerlink" title="⑤日志审计"></a>⑤日志审计</h3><p>提供访问日志的存储和查询功能，可满足您对企业数据的监控审计需求。同时，企业安全风险人员会要求开启日志审计，在必要时审核数据是否有泄漏风险。</p><p><strong>如何监控审计数据被访问日志？</strong></p><p>作为企业安全风险管理人员，通常都需要通过日志、监控、审计等来查看数据是否有泄漏风险，审核企业人员行为，对象存储如何支持这些需求呢？</p><ul><li><strong>提供访问日志持久化存储和实时查询</strong> 。访问对象存储 OSS 的过程中会产生大量的访问日志，可以转存到 OSS 自身进行成本优化；同时通过实时查询能力，帮助完成访问统计、异常事件回溯和问题定位等工作。</li><li><strong>监控告警</strong> 。提供系统基本运行状态、性能以及计量等方面的监控数据指标，并提供自定义报警服务，帮助跟踪请求、分析使用情况、统计业务趋势，及时发现以及诊断系统的相关问题。</li></ul><h3 id="⑥合规认证"><a href="#⑥合规认证" class="headerlink" title="⑥合规认证"></a>⑥合规认证</h3><p>通过Cohasset Associates审计认证、FINRA 4511、CFTC 1.31、ISO、BS10012、CSA STAR等多项合规认证，能够满足您的多种合规要求。企业管理层会则非常关注数据安全，保证业务高可靠、高可用、合规设计等方面有良好的功能支撑。</p><p><strong>如何做到数据安全的数据不丢不错的高可靠？</strong></p><p> <strong>如何做到数据安全的访问高可用？</strong></p><p><strong>如何做到全面的数据保护功能？</strong></p><h2 id="五、对象存储防护总结"><a href="#五、对象存储防护总结" class="headerlink" title="五、对象存储防护总结"></a>五、对象存储防护总结</h2><ol><li><strong>加强身份验证和访问控制：</strong> 使用身份和访问管理（IAM）来限制对存储桶和其中对象的访问。确保只有授权的用户或服务能够访问，并严格控制他们的权限，采用最小权限原则。</li><li><strong>加密数据：</strong> 对于敏感数据，采用适当的加密措施，包括数据在传输和静态存储时的加密。</li><li><strong>网络安全配置：</strong> 配置网络安全组、防火墙等措施，限制对存储桶的访问仅来自可信来源，减少公开访问的风险。</li><li><strong>监控和日志记录：</strong> 设置监控警报，对存储桶的访问和活动进行实时监控，并记录审计日志，以便及时发现异常行为或潜在的安全威胁。</li><li><strong>定期备份和恢复：</strong> 定期备份存储桶中的重要数据，并建立有效的恢复计划，以防止数据丢失或损坏，例如意外删除或勒索软件攻击。</li><li><strong>防止公开访问误配置：</strong> 定期审查存储桶的访问权限配置，确保没有意外的公开访问权限，避免因配置错误导致数据泄露的风险。</li><li><strong>实施访问限制策略：</strong> 使用 IP 白名单或访问令牌等策略，限制存储桶的访问仅限于授权的用户或系统</li></ol><h1 id="📚参考资料"><a href="#📚参考资料" class="headerlink" title="📚参考资料"></a>📚参考资料</h1><ul><li><a href="https://segmentfault.com/a/1190000044894267">对象存储安全的最佳实践和一些反面的案例 - 待注销 - SegmentFault 思否</a></li><li><a href="https://www.alibabacloud.com/help/zh/oss/security-and-compliance/monitoring-and-audit">监控审计 - 对象存储 OSS - 阿里云 (alibabacloud.com)</a></li><li><a href="https://developer.aliyun.com/article/989563">如何安全存放数据到对象存储 OSS 及数据湖的13问-阿里云开发者社区 (aliyun.com)</a></li><li><a href="https://mp.weixin.qq.com/s/VcE9L5Jjcm8bWf9AeYcO9A">一文了解阿里云对象存储OSS (qq.com)</a></li></ul><h1 id="❤️Sponsor"><a href="#❤️Sponsor" class="headerlink" title="❤️Sponsor"></a>❤️Sponsor</h1><p>您的支持是我不断前进的动力，如果您感觉本文对您有所帮助的话，可以考虑打赏一下本文，用以维持本博客的运营费用，拒绝白嫖，从你我做起！🥰🥰🥰</p><table>  <tbody>     <tr>         <td style="text-align:center;">支付宝</td>         <td style="text-align:center;">微信</td>     </tr>   <tr>    <td style="text-align:center;" ><img width="200" src="https://jwt1399.top/medias/reward/alipay.png"></td>          <td style="text-align:center;"><img width="200" src="https://jwt1399.top/medias/reward/sponor_wechat.png"></td>       </tr></tbody></table>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;对象存储服务（Object Storage</summary>
        
      
    
    
    
    <category term="Data" scheme="https://jwt1399.top/categories/Data/"/>
    
    
    <category term="OSS" scheme="https://jwt1399.top/tags/OSS/"/>
    
    <category term="Security" scheme="https://jwt1399.top/tags/Security/"/>
    
  </entry>
  
  <entry>
    <title>数据安全-指北（更新中）</title>
    <link href="https://jwt1399.top/posts/16668.html"/>
    <id>https://jwt1399.top/posts/16668.html</id>
    <published>2024-07-03T08:00:38.000Z</published>
    <updated>2024-08-25T06:53:56.533Z</updated>
    
    <content type="html"><![CDATA[<h2 id="⓪数据安全定义"><a href="#⓪数据安全定义" class="headerlink" title="⓪数据安全定义"></a>⓪数据安全定义</h2><p>《中华人民共和国数据安全法》中第三条，给出了数据安全的定义，是指通过采取必要措施，确保数据处于有效保护和合法利用的状态，以及具备保障持续安全状态的能力。要保证<strong>数据处理的全过程安全</strong>，数据处理，包括数据的<strong>收集、存储、使用、加工、传输、提供、公开</strong>等。</p><p>信息安全或数据安全有对立的两方面含义：</p><ul><li>一是<strong>数据本身</strong>的安全，主要是指采用现代密码算法对数据进行主动保护，如数据保密、数据完整性、双向强身份认证等。</li><li>二是<strong>数据防护</strong>的安全，主要是采用现代信息存储手段对数据进行主动防护，如通过磁盘阵列、数据备份、异地容灾等手段保证数据的安全。</li></ul><h2 id="①数据防泄漏（DLP）"><a href="#①数据防泄漏（DLP）" class="headerlink" title="①数据防泄漏（DLP）"></a>①数据防泄漏（DLP）</h2><h3 id="DLP-简介"><a href="#DLP-简介" class="headerlink" title="DLP 简介"></a>DLP 简介</h3><p>DLP即常说的数据泄露防护解决方案。其核心是能识别出数据中的内容并为之分类。这些数据可以电子邮件、文件、数据包、应用程序或者数据存储等形式存在。不论数据状态是仅被存储中，或者正在被使用的，亦或者在网络中传输，都能被检测。DLP还应支持根据既定的策略对敏感信息的检测和使用情况提供日志、标记、加密、权限控制和阻断等操作。</p><ul><li><a href="https://www.h3c.com/cn/Products_And_Solution/Proactive_Security/Product_Series/Data_Security/DLP2000/DLP2000/DLP2000/">H3C SecPath DLP2000系列网络数据防泄漏系统-新华三集团-H3C</a></li><li><a href="https://www.dbappsecurity.com.cn/product/cloud3013.html">AiDLP数据防泄漏系统_数据安全防泄漏系统_安恒信息（上市公司） (dbappsecurity.com.cn)</a></li></ul><h3 id="为什么需要DLP"><a href="#为什么需要DLP" class="headerlink" title="为什么需要DLP"></a><strong>为什么需要DLP</strong></h3><p>计算机和互联网已成为日常办公、通信交流和协作互动的必备工具和途径。但是，信息系统在提高人们工作效率的同时，也对数据的存储安全及传输安全提出了更高要求。</p><ul><li>法律法规也更为严格。GDPR、HIPAA、中华人民共和国数据安全法等都要求企业保护数据资产，防止敏感信息泄漏。数据泄漏事件使企业承担法律风险。</li><li>需要保护的数据更多。个人信息（帐号、电话、地址等）、知识产权（产品设计、研发图纸等）、商业机密（预算、规划、工资单等），任何数据的泄漏都会给企业带来不可估量的损失。</li><li>数据不一定是黑客窃取的。由于内部员工有意或无意的行为导致的数据泄漏事件日益增多，比如邮件误发、U盘丢失、机密文件打印等等。</li><li>传统安全方案无法提供合适数据泄漏防护。传统安全方案使用传统防火墙、入侵检测、存储加密系统等安全设备，通过限制数据访问或全网数据加密等防护手段提供被动、囚笼式的防护。不利于数据流动，无法控制权限内用户的泄密，也无法识别敏感信息进而实现对企业内数据的分类管控和审计。</li></ul><h3 id="DLP工作原理"><a href="#DLP工作原理" class="headerlink" title="DLP工作原理"></a>DLP工作原理</h3><blockquote><p>识别出敏感信息，并根据预定义的规则保护这些数据是DLP的工作。</p></blockquote><h4 id="数据获取"><a href="#数据获取" class="headerlink" title="数据获取"></a>数据获取</h4><p>识别信息的前提是获取到数据。DLP通常通过以下方式获取企业内网数据：</p><ul><li><p>扫描终端和服务器存储。</p></li><li><p>监控网络协议链接，还原传输的文件。</p></li><li><p>监控应用和驱动，提取传送和使用的数据。</p></li></ul><h4 id="数据识别"><a href="#数据识别" class="headerlink" title="数据识别"></a>数据识别</h4><p>并不是所有数据都需要防护，包含敏感信息的数据才是DLP的防护对象。获取到数据后，DLP必须进行内容识别和检测以确定是否包含敏感信息，不论数据以邮件、PDF、Word还是任何形式存在。</p><p>内容识别和检测技术分为常规检测技术和高级检测技术。常规检测技术主要包括<strong>正则表达式检测、关键字检测、文档属性检测</strong>等。高级检测技术包括<strong>索引内容指纹匹配技术（IDM）、精确内容指纹匹配技术（EDM）、计算机视觉技术</strong>等。</p><p>此处简单介绍一下这三种高级检测技术。</p><ul><li><p>索引内容指纹匹配技术（IDM）：将待检测的内容或文档，与Word文件、PPT文件、PDF文件、各种源程序文件等非结构化储存的样本文档进行匹配，获得相似度，并以此判断其是否源自样本文档库。该技术从样本文档中生成指纹特征库， 然后以同样的方法从待检测文档或内容中提取指纹；将得到的指纹与指纹库进行匹配，获得其相似度。</p></li><li><p>精确内容指纹匹配技术（EDM）：将待检测的文档或内容，与Excel、数据库表等结构化储存的数据源表格之间进行精确匹配，判断其是否摘录自数据源表格。客户在数据源表格中指定特定列，为其生成指纹特征库，以此检测目标内容（无论何种文件格式）是否存在与表格中特定列相匹配的数据。</p></li><li><p>计算机视觉技术：将待检测的图像，提取其轮廓特征后，与储存的样本图像特征进行相似度匹配，并以此判断其是否源自样本图像库。利用图像处理技术提取图像的轮廓特征，并对特征进行矢量化编码；使用相似度匹配技术对特征库进行匹配。即使对图像进行缩放、部分裁剪、添加水印、改变明亮度，也能够很好的匹配。</p></li></ul><h4 id="数据控制"><a href="#数据控制" class="headerlink" title="数据控制"></a>数据控制</h4><p>识别出敏感信息后，根据既定的策略，对承载这些信息的文件进行控制。控制行为包括但不限于：</p><ul><li><p>数据加密：对于敏感数据加密，即使数据不当流出，其中的敏感信息也不会泄露。</p></li><li><p>权限识别和控制：数据和用户都有权限属性，只有权限相符合的用户才能访问对应的数据。且访问时数据会自动解密，不影响用户体验。</p></li><li><p>非法行为阻断：对于非法外发、拷贝、打印等动作进行阻断或者通报告警。</p></li><li><p>数据可视化呈现：全网敏感数据分布的可视化呈现。</p></li><li><p>数据审计：对敏感信息的整个生命周期（生成、流转、使用、销毁等操作）做详细的日志和报表。</p></li></ul><h2 id="②用户和实体行为分析（UEBA）"><a href="#②用户和实体行为分析（UEBA）" class="headerlink" title="②用户和实体行为分析（UEBA）"></a>②用户和实体行为分析（UEBA）</h2><p><strong>UEBA（User and Entity Behavior Analytics，用户和实体行为分析）</strong>主要用于检测用户以及网络中实体（网络设备、进程、应用程序等）的异常行为，然后判断异常行为是否存在安全威胁，并及时向运维人员发出告警。</p><p>UEBA利用人工智能和机器学习算法来检测网络中的用户和实体的异常行为。首先，UEBA收集有关用户和实体活动的数据，通过分析数据来建立用户和实体的行为模式基线。然后，UEBA会持续监控用户和实体行为，并将其当前行为与基线行为进行比较，计算风险评分，确定行为偏差是否可接受。如果风险评分超过一定的阈值，UEBA会实时向用户发出告警。</p><p>详情请参考：<a href="https://developer.aliyun.com/article/1320670">什么是用户实体行为分析（UEBA）</a></p><p><strong>厂商 UEBA 产品</strong></p><ul><li><a href="https://www.dbappsecurity.com.cn/product/cloud127.html">AiThink用户与实体行为分析系统-安恒信息 (dbappsecurity.com.cn)</a></li></ul><h2 id="③安全信息与事件管理（SIEM）"><a href="#③安全信息与事件管理（SIEM）" class="headerlink" title="③安全信息与事件管理（SIEM）"></a>③安全信息与事件管理（SIEM）</h2><p><strong>SIEM（Security Information and Event Management，安全信息与事件管理）</strong>是一种网络安全技术，通过收集和分析不同来源（主机、应用、安全、网络等）的日志事件及相关数据，提供安全事件的实时监控和管理等功能。</p><p>SIEM通过安全事件帮助用户识别异常事件，查看安全态势，并在出现异常安全事件和趋势时向用户发出告警信息。UEBA的工作方式与SIEM类似，只不过UEBA通过用户和实体的行为信息来判断是否存在安全威胁。</p><p>SIEM系统将来自不同内部安全工具的安全事件数据汇聚到单个日志中并进行分析，以检测异常行为和潜在威胁。UEBA 可通过其内部威胁检测和用户行为分析功能将 SIEM 可见性扩展到网络中。如今，<strong>许多 SIEM 解决方案均包含 UEBA</strong>。</p><p><strong>厂商 SIEM 产品</strong></p><ul><li><a href="https://e.huawei.com/cn/products/security/cis">HiSec Insight-安全态势感知系统-华为企业业务</a></li><li><a href="https://www.h3c.com/cn/Service/Document_Software/Document_Center/IP_Security/SecCenter/H3C_SecCenter_CSAP/Configure/Operation_Manual/H3C_SecCenter_Web_E1143_E1144_E1-3007/00/?CHID=678824">00-安全威胁发现与运营管理平台简介-新华三集团-H3C</a></li><li><a href="https://www.dbappsecurity.com.cn/product/cloud62.html">安恒AiLPHA安全分析与管理平台 (dbappsecurity.com.cn)</a></li></ul><h2 id="④终端检测和响应（EDR）"><a href="#④终端检测和响应（EDR）" class="headerlink" title="④终端检测和响应（EDR）"></a>④终端检测和响应（EDR）</h2><p><strong>EDR（Endpoint Detection and Response，终端&#x2F;端点检测和响应）</strong>用来指代一种端点（终端PC、服务器、云系统、移动设备或物联网设备等）安全防护解决方案。它记录端点上的行为，使用数据分析和基于上下文的信息检测来发现异常和恶意活动，并记录有关恶意活动的数据，使安全团队能够调查和响应事件。EDR解决方案通常提供威胁搜寻、检测、分析和响应功能。</p><img src="https://img.jwt1399.top/img/202408251244902" style="zoom:50%;" /><h3 id="EDR的工作原理"><a href="#EDR的工作原理" class="headerlink" title="EDR的工作原理"></a><strong>EDR的工作原理</strong></h3><p>传统终端安全软件的机制，是客户端重载，威胁检测几乎全部在终端侧完成，终端用户的工作经常被打扰，而且系统资源开销很大（尤其杀软、桌管等多套客户端并行状况下）</p><p>而EDR客户端只负责事件收集，然后将可以事件实时同步到服务器端&#x2F;云端，通过服务端进行大数据检测、分析、情报匹配，以及专家介入研判处置。</p><p><img src="https://img.jwt1399.top/img/202408251245048.gif"></p><p>EDR对终端用户几乎无打扰，充分利用云端强大的计算资源和检测引擎，迅速发现可疑行为。</p><img src="https://img.jwt1399.top/img/202408251234186.gif" style="zoom:50%;" /><p>传统终端病毒和威胁检测的思路，就像蹲守在羊圈门口的守卫，时刻检查所有过往行人，草木皆兵，看见可疑的立即抓走。不仅消耗体力，还容易搞出“狼来了”式的误报，更可怕的是被针对性的“绕过”（例如前面所说的无文件攻击）。</p><p>EDR则是“以退为进”，不关心行人的仪表特征，就看他们干不干坏事。如果有人干了破坏羊圈的事，它就会把这种行为定性为威胁事件，迅速告警和响应，找人修复羊圈，并溯源活捉坏人。羊圈被破坏不要紧，羊没丢就是完成了核心KPI，还顺手捉到了高级狼。</p><p><strong>所以，EDR与杀软、桌管们的职责分工完全不一样。</strong>初级的老破小病毒、桌面违规操作管理等等，交给杀软、桌管来处理。EDR负责对付各类高级的、潜伏性强的、未来危害性大的、甚至被武器化的威胁。</p><h3 id="EDR-能力"><a href="#EDR-能力" class="headerlink" title="EDR 能力"></a>EDR 能力</h3><p><strong>1、事件采集和传输</strong></p><p><strong>终端事件采集是后续检测、分析能力的重要基础</strong></p><p>真正的EDR客户端，都是极简客户端，或者说只是一个探针。既要足够轻，对终端资源极低占用，对终端用户零打扰，又要有极为优秀的行为采集能力，保证在恶意程序干坏事的时候，可以明察秋毫。</p><p>采集完成需要送到服务端，进行情报匹配、AI研判、大数据分析</p><p>这就需要过滤无效事件、最大化传输有效事件、最小化网络带宽消耗。如何压缩、去重、组合。</p><p><strong>2、威胁检测</strong></p><p><strong>搞定事件收集和传输，接下来的挑战就是，如何精准、高效地检测威胁。</strong></p><p><strong>3、溯源分析</strong></p><p><strong>快速精准检测出威胁事件还不够，还要完成威胁溯源。</strong></p><p>溯源意义在于找到真正的元凶，从源头把入侵的黑手斩断。但是，溯源的难度在于：进程的源头，是正常的系统服务；执行的源头，是合法的系统进程；事件的源头，是跨终端的RPC调用。</p><img src="https://img.jwt1399.top/img/202408251243782"  style="zoom:25%;" /><p><strong>4、事件响应</strong></p><p><strong>最后一步，需要对威胁事件做出响应：该修修、该补补、该抓抓、该shasha。</strong></p><p>这个响应，不是简单的隔离机器或者进程，还需要配套持久化、序列化、智能化的清理动作，除恶务尽。</p><p>对于常见、多发的威胁，需要自动化的清理处置能力。对于复杂威胁，还要进一步调查取证。</p><p><strong>厂商 EDR 产品</strong></p><ul><li><p><a href="https://e.huawei.com/cn/products/security/cloudservice">华为乾坤-安全云服务-防勒索安全-华为企业业务 (huawei.com)</a></p></li><li><p><a href="https://e.huawei.com/cn/solutions/enterprise-network/security/smb-anti-risk-security">智能中小企业防勒索解决方案-办公园区网络安全-华为企业业务 (huawei.com)</a></p></li></ul><h2 id="⑤入侵和攻击模拟（BAS）"><a href="#⑤入侵和攻击模拟（BAS）" class="headerlink" title="⑤入侵和攻击模拟（BAS）"></a>⑤入侵和攻击模拟（BAS）</h2><p>BAS（<code>Breach and Attack Simulation</code>，入侵和攻击模拟）提供自动化的安全防御有效性验证。，是指通过主动验证 +（半）自动化的方式，利用攻击者的战术、技术和程序来模拟杀伤链的不同阶段，<strong>持续测试和验证</strong>现有网络整体的安全机制（包括各安全节点是否正常工作、安全策略与配置的有效性、检测&#x2F;防护手段是否按预期运行等），对企业对抗外部威胁的能力进行<strong>量化评估</strong>，同时提供<strong>改进建议</strong>，推动企业安全体系走向成熟。</p><p>从攻击为中心的视角来看待风险，基于真正的威胁来排序自己的修复行为。像黑客那样思考和工作，就是最好的防御方式。在出现新型APT攻击、勒索病毒、挖矿病毒时，BAS可根据可靠的威胁情报，自动化在当前网络环境中进行模拟攻击流程，以此来印证安全控制设备是否已经支持防御或监测该类型的攻击。举个例子，在发生0day漏洞后，安全厂商会推出相应补丁包，从用户视角需要快速验证此补丁在面对真实攻击时的有效性。</p><h3 id="BAS-的缺陷与挑战"><a href="#BAS-的缺陷与挑战" class="headerlink" title="BAS 的缺陷与挑战"></a><strong>BAS 的缺陷与挑战</strong></h3><p>尽管 BAS 产品能够自动化模拟攻击，帮助企业评估网络安全防御能力，但它也存在一些固有的挑战和缺陷。在实际应用中，我们常常会<strong>模拟攻击的局限性</strong>，这些问题让很多企业感到困惑。BAS 工具不能完全替代人工操作，在复杂场景和高级攻击中仍需要人工参与。企业应根据自身需求和资源情况，合理部署 BAS 工具，并结合其他安全措施，实现更全面的安全防护。</p><p><strong>（1）模拟攻击的挑战</strong></p><p><strong>模拟攻击的局限性</strong>：BAS工具在模拟攻击时，可能由于自身设计和技术限制，无法完全模拟某些复杂的攻击手法或真实场景。这可能导致模拟结果与实际攻击情况有所差异。应该<strong>场景多样化</strong>：不断更新和扩展攻击模拟场景，尽可能涵盖各种攻击手法和攻击链。与最新威胁情报和安全研究相结合，确保模拟攻击的全面性和时效性。<strong>动态调整</strong>：根据企业的实际环境和网络架构，动态调整模拟攻击的策略和方法，以提高模拟结果的相关性和实用性。</p><p><strong>（2）攻击模拟的覆盖范围有限</strong></p><p>BAS 产品的攻击模拟通常基于预定义的攻击模板或脚本，这意味着其模拟的攻击场景范围有限。对于复杂的、高度定制化的攻击，BAS 工具的能力可能不足。与人工渗透测试相比，BAS 的自动化攻击手段标准化程度高，缺乏灵活性，难以应对真实环境中的复杂攻击。</p><p><strong>应对参考：</strong>BAS 工具需要不断扩展其攻击模板库，并允许用户自定义攻击脚本，以增强对复杂场景的模拟能力。此外，与人工红队测试配合使用，可以弥补 BAS 在高级攻击场景中的不足。</p><p><strong>（3）对环境的适应性有限</strong></p><p>BAS 工具在不同企业环境中的适应性往往有限。例如，企业的网络架构、应用程序环境和安全需求各不相同，BAS 工具可能需要大量配置和自定义才能适应这些差异化环境。随着企业的网络和业务不断发展，BAS 工具的适应性也需要相应调整。</p><p><strong>应对参考</strong>：BAS 产品应具备较强的可定制性，能够根据不同企业的特定需求灵活调整。同时，厂商应提供充分的技术支持和培训，帮助用户最大化利用 BAS 工具。</p><p><strong>（4）无法完全替代人工</strong></p><p>虽然 BAS 工具能够自动化模拟攻击场景，但在一些高级渗透测试和复杂网络环境中，人工操作仍然不可或缺。BAS 的自动化攻击能力有限，难以模拟人类攻击者的灵活性和策略性，而人工红队的操作更具针对性和效果。</p><p><strong>应对参考</strong>：企业在使用 BAS 工具时，仍需结合人工红队测试，以确保全面的安全评估。此外，BAS 产品应不断优化自动化功能，提升其在更复杂场景下的能力。</p><p><strong>（5）需要较高的专业知识来操作</strong></p><p>虽然 BAS 工具旨在简化安全测试，但它的配置和使用通常需要较高的专业知识。例如，安全团队需要具备网络安全的背景知识才能有效构建攻击场景、分析仿真结果以及调整模拟参数。这对一些技术能力较弱的中小企业来说可能构成门槛。</p><p><strong>应对参考</strong>：BAS 供应商应提供更直观的用户界面和详细的文档支持，以降低使用门槛。此外，提供培训和技术支持服务，可以帮助企业更好地部署和运用 BAS 产品。</p><p><strong>（6）资源消耗大</strong></p><p>BAS 工具在运行过程中可能会消耗大量的网络资源，尤其是在大规模仿真攻击时。大量的模拟攻击流量可能导致企业的网络性能下降，甚至干扰正常业务运营。</p><p><strong>应对参考</strong>：BAS 产品应具备优化资源使用的功能，例如通过调整仿真流量的优先级或在非高峰期进行模拟测试，来减少对企业网络资源的影响。</p><p><strong>（7）与其他安全工具的集成性有限</strong></p><p>虽然 BAS 产品可以生成详细的攻击模拟报告，但如果无法与企业现有的安全工具（如 SIEM、EDR、SOC 等）无缝集成，其价值可能会受到限制。<strong>缺乏集成可能导致信息孤岛</strong>，使 BAS 无法充分发挥其潜力。</p><p><strong>应对参考</strong>：BAS 工具应支持与其他主流安全工具的集成，并提供 API 或插件接口，以便在企业的整体安全生态系统中更好地发挥作用。</p><h3 id="BAS-在红蓝演练中的角色"><a href="#BAS-在红蓝演练中的角色" class="headerlink" title="BAS 在红蓝演练中的角色"></a>BAS 在红蓝演练中的角色</h3><p>BAS 产品无法完全替代红队的手动攻击能力。目前，BAS 产品主要用于自动化、常规化的攻击模拟，而红队更注重针对性和复杂性。<strong>BAS 产品应定位为红队与蓝队的辅助工具</strong>，而不是替代品。BAS 可以通过自动化手段完成基础的攻击场景模拟，帮助蓝队发现防御中的盲点，从而让红队集中精力在更高难度的攻击场景中。</p><p>未来，随着 BAS 产品技术的进步，我们可能会看到 BAS 工具与红队活动的更紧密集成。通过与红队的协作，BAS 可以充当持续测试和反馈循环的一部分，不断提升企业的整体安全水平。</p><p><strong>BAS 产品与其他网络安全工具，如漏洞扫描、渗透测试、自动化渗透平台，以及红蓝对抗，存在着一定的区别和重叠。</strong></p><p>未来 BAS 产品的市场定位将更加精细化。<strong>BAS 不应该试图取代其他安全工具</strong>，而是与之形成互补。BAS 不应该试图取代其他安全工具，而应与其形成互补。在我看来，BAS 可以与漏洞扫描结合，进行精准的攻击模拟，这样才能更有效地评估漏洞的实际风险。国内厂商如果能够在这方面做到极致，将有机会在市场中脱颖而出，展示自己的独特价值。而与渗透测试相比，BAS 可以承担自动化、常规化的任务，将更多的复杂攻击场景留给渗透测试专家。</p><h3 id="实施BAS的关键步骤"><a href="#实施BAS的关键步骤" class="headerlink" title="实施BAS的关键步骤"></a><strong>实施BAS的关键步骤</strong></h3><p><strong>1、识别网络中的脆弱区域</strong></p><p>在进行BAS活动之前，组织应该提前确定网络中存在较大风险隐患的脆弱区域。这可以通过不同的方式完成，包括漏洞扫描、人工渗透测试和安全性审计。这主要目的是了解潜在的攻击面，并确定任何弱点和漏洞。</p><p>例如，对网络进行漏洞扫描，以识别任何可能易受攻击的过时软件或未打补丁的系统。一旦确定了网络中的脆弱区域，就可将其作为BAS测试工作的起点。在此过程中，组织可以优先识别出哪些领域值得重点关注。</p><p><strong>2、创建基线安全模型</strong></p><p>当企业发现并确定了脆弱区域，接下来要做的就是建立基线安全模型。这个特定的模型将表示公司安全控制的当前状态。它可以为后续的安全性衡量改进工作提供必要的参考。</p><p>例如，基线安全模型应该包括公司防火墙、入侵检测系统和其他安全控制的当前配置。当企业开始执行BAS活动之后，它可以作为度量其安全控制有效性的起点。</p><p><strong>3、进行模拟攻击测试</strong></p><p>当以上的工作完成后，企业接下来要做的就是进行BAS练习。这意味着要根据公司的关注点和需求进行一系列测试。这一系列测试可以包括在网络、端点、web应用程序、电子邮件系统、无线网络或云基础设施上进行测试。例如，如果公司关心的是基于电子邮件的安全控制。他们将进行模拟网络钓鱼攻击，以测试电子邮件安全控制的有效性。它包括向不同的员工发送虚假的网络钓鱼电子邮件，并测量有多少人受到攻击。</p><p> <strong>4、分析结果并改进</strong></p><p>在成功的BAS应用中，企业还必须及时分析结果并改进公司的安全控制。组织需要审查由BAS工具提供的每一份报告和分析，并找到需要改进的地方。例如，回到网络钓鱼的例子，一旦公司采取并衡量了结果，它就可以培训这些员工，并向他们提供更多关于网络安全协议的信息，甚至可以让他们参加研讨会。组织可能还需要实现额外的电子邮件安全控制，例如双因素身份验证或高级垃圾邮件过滤。</p><h3 id="厂商-BAS-产品"><a href="#厂商-BAS-产品" class="headerlink" title="厂商 BAS 产品"></a><strong>厂商 BAS 产品</strong></h3><p><a href="https://www.attackiq.com/">Breach and Attack Simulation with MITRE ATT&amp;CK - AttackIQ</a></p><p><a href="https://www.safebreach.com/">SafeBreach | Breach and Attack Simulation Platform</a></p><p><a href="https://cymulate.com/">Cymulate - Exposure Management &amp; Security Validation Platform</a></p><h2 id="⑥数据脱敏（DMS）"><a href="#⑥数据脱敏（DMS）" class="headerlink" title="⑥数据脱敏（DMS）"></a>⑥数据脱敏（DMS）</h2><h3 id="数据脱敏定义"><a href="#数据脱敏定义" class="headerlink" title="数据脱敏定义"></a>数据脱敏定义</h3><p>数据脱敏（Data Masking Service）是一种信息安全技术，旨在保护敏感信息和隐私数据，防止未经授权的访问或泄露。它通过对原始数据进行有策略的修改或替换，创建一个看上去与原数据相似但不含真正敏感细节的数据副本，以供非生产环境如开发、测试、分析或培训等用途中安全使用。</p><ul><li>静态数据脱敏：在数据被提取并复制到非生产环境之前一次性完成脱敏处理。适用于数据外发场景，如提供给第三方或用于测试数据库。</li><li>动态数据脱敏：在数据查询过程中实时进行，当用户访问敏感数据时，系统自动对其进行脱敏处理。适用于直接连接生产数据库的场景，确保即使查看数据的行为也不会暴露敏感信息。</li></ul><h3 id="数据脱敏的目的"><a href="#数据脱敏的目的" class="headerlink" title="数据脱敏的目的"></a>数据脱敏的目的</h3><ul><li>保护隐私：确保个人信息如身份证号、电话号码、银行账号等不被非法获取和利用。</li><li>合规要求：满足行业规范和法律法规对数据保护的要求，如GDPR（欧盟通用数据保护条例）等。</li><li>安全测试：在不影响真实数据安全的前提下，为软件测试、系统调试提供接近真实的测试数据。</li><li>降低风险：即便数据被非法访问，由于已脱敏，实际敏感信息不会泄露，降低了数据泄露的风险。</li></ul><h3 id="数据脱敏方案"><a href="#数据脱敏方案" class="headerlink" title="数据脱敏方案"></a>数据脱敏方案</h3><p>数据脱敏系统可以按照不同业务场景自行定义和编写脱敏规则，可以针对库表的某个敏感字段，进行数据的不落地脱敏。</p><h4 id="1、无效化"><a href="#1、无效化" class="headerlink" title="1、无效化"></a>1、无效化</h4><p>无效化方案在处理待脱敏的数据时，通过对字段数据值进行<code>截断</code>、<code>加密</code>、<code>隐藏</code> 等方式让敏感数据脱敏，使其不再具有利用价值。一般采用特殊字符（<code>*</code>等）代替真值，这种隐藏敏感数据的方法简单，但缺点是用户无法得知原数据的格式，如果想要获取完整信息，要让用户授权查询。</p><h4 id="2、随机值"><a href="#2、随机值" class="headerlink" title="2、随机值"></a>2、随机值</h4><p>随机值替换，字母变为随机字母，数字变为随机数字，文字随机替换文字的方式来改变敏感数据，这种方案的优点在于可以在一定程度上保留原有数据的格式，往往这种方法用户不易察觉的。</p><h4 id="3、数据替换"><a href="#3、数据替换" class="headerlink" title="3、数据替换"></a>3、数据替换</h4><p>数据替换与前边的无效化方式比较相似，不同的是这里不以特殊字符进行遮挡，而是用一个设定的虚拟值替换真值。比如说我们将手机号统一设置成 “13651300000”。</p><h4 id="4、对称加密"><a href="#4、对称加密" class="headerlink" title="4、对称加密"></a>4、对称加密</h4><p>对称加密是一种特殊的可逆脱敏方法，通过加密密钥和算法对敏感数据进行加密，密文格式与原始数据在逻辑规则上一致，通过密钥解密可以恢复原始数据，要注意的就是密钥的安全性。</p><h4 id="5、平均值"><a href="#5、平均值" class="headerlink" title="5、平均值"></a>5、平均值</h4><p>平均值方案经常用在统计场景，针对数值型数据，我们先计算它们的均值，然后使脱敏后的值在均值附近随机分布，从而保持数据的总和不变。</p><h4 id="6、偏移和取整"><a href="#6、偏移和取整" class="headerlink" title="6、偏移和取整"></a>6、偏移和取整</h4><p>这种方式通过随机移位改变数字数据，偏移取整在保持了数据的安全性的同时保证了范围的大致真实性，比之前几种方案更接近真实数据，在大数据分析场景中意义比较大。</p><p><strong>厂商数据脱敏 产品</strong></p><ul><li><a href="https://www.h3c.com/cn/Products_And_Solution/Proactive_Security/Product_Series/Data_Security/DM2000_D/DM2000_D/DM2000_D/">H3C SecPath DM2000-D系列数据动态脱敏系统-新华三集团-H3C</a></li><li><a href="https://www.h3c.com/cn/Products_And_Solution/Proactive_Security/Product_Series/Data_Security/DM2000_S/DM2000_S/DM2000_S/">H3C SecPath DM2000-S系列数据静态脱敏系统-新华三集团-H3C</a></li><li><a href="https://www.dbappsecurity.com.cn/product/cloud2803.html">AiMask数据脱敏系统_数据库脱敏_数据安全产品_安恒信息 (dbappsecurity.com.cn)</a></li><li><a href="https://www.nsfocus.com.cn/html/2020/208_1214/140.html">绿盟数据脱敏系统 DMS - 数据安全产品 - 绿盟科技-巨人背后的专家 (nsfocus.com.cn)</a></li></ul><h2 id="⑦数据资产地图"><a href="#⑦数据资产地图" class="headerlink" title="⑦数据资产地图"></a>⑦数据资产地图</h2><p>数据资产地图可以通过可视化的手段，从资产概况、分类分级、权限配置、数据存储、敏感数据以及数据出口分析等多种维度查看资产的安全状况。可协助您快速发现风险资产并进行快速风险处理操作。</p><ul><li><a href="https://www.h3c.com/cn/Products_And_Solution/Proactive_Security/Product_Series/Data_Security/DA2000/DA2000/DA2000/">H3C SecPath DA2000数据资产管理系统-新华三集团-H3C</a></li><li><a href="https://support.huaweicloud.com/dsc/index.html">成长地图_数据安全中心 DSC (huaweicloud.com)</a></li></ul><h2 id="⑧数据分类分级"><a href="#⑧数据分类分级" class="headerlink" title="⑧数据分类分级"></a>⑧数据分类分级</h2><h3 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h3><p>根据《GB&#x2F;T 38667-2020 信息技术-大数据-数据分类指南》的定义，<strong>数据分类是根据数据的属性或特征，按照一定的原则和方法进行区分和归类，以便更好地管理和使用数据。</strong>数据分类不存在唯一的分类方式，会依据企业的管理目标、保护措施、分类维度等形成多种不同的分类体系。</p><p>数据分类是数据资产管理的第一步。不论是对数据资产进行编目、标准化，还是数据的确权、管理，或是提供数据资产服务，进行有效的数据分类都是其首要任务。数据分类更多是从业务角度或数据管理的方向考量的，包括行业维度、业务领域维度、数据来源维度、共享维度、数据开放维度等。同时，根据这些维度，将具有相同属性或特征的数据，按照一定的原则和方法进行归类。</p><p><strong>数据分级则是按数据的重要性和影响程度区分等级，确保数据得到与其重要性和影响程度相适应的级别保护。</strong>影响对象一般是三类对象，分别是国家安全和社会公共利益、企业利益（包括业务影响、财务影响、声誉影响）、用户利益（用户财产、声誉、生活状态、生理和心理影响）。企业建议选取影响程度中的最高影响等级为该数据对象的重要敏感程度。同时，数据定级可根据数据的变化进行升级或降级，例如包括数据内容发生变化、数据汇聚融合、国家或行业主管要求等情况引起的数据升降级。数据分级本质上就是数据敏感维度的数据分类。</p><h3 id="实施路径"><a href="#实施路径" class="headerlink" title="实施路径"></a><strong>实施路径</strong></h3><p>在实际落地过程中，通常会把数据分类分级的实施路径总结成为五步：</p><p><strong>第一步，咨询调研分析。</strong>基于行业相关的监管政策和标准规范，对业务系统、数据资产现状和数据安全现状等进行全面调研分析，从而对企业业务、数据及安全现状做到“心中有数”。</p><p><strong>第二步，数据资产梳理</strong>。自动化识别数据资产，对数据资产进行梳理打标，构建好数据资产目录和数据资产清单，为企业数据分类分级打好基础。</p><p><strong>第三步，数据分类方案。</strong>基于数据资产清单进行数据分类体系设计，完成数据分类打标实施。打标实施完之后，再进行分类分级规则调优，提升自动化分类的比例和准确率。</p><p><strong>第四步，数据分级方案。</strong>先进行数据分级体系设计，接下来进行数据分级的规则调优，尽量提升自动化分级的覆盖率和准确率，降低人工成本，然后是数据等级变更维护机制和工具平台设置。</p><p><strong>第五步，数据分类分级全景图。</strong>构建数据分类分级清单，实现数据分类分级可视化。同时产出一些数据分类分级运营机制，为数据安全分级保护打好基础，做好准备。</p><ul><li><a href="https://www.dbappsecurity.com.cn/product/cloud131.html">Aisort数据安全分级与风险评估系统-安恒信息(上市公司) (dbappsecurity.com.cn)</a></li></ul><h2 id="⑨入侵检测检测（IDS）"><a href="#⑨入侵检测检测（IDS）" class="headerlink" title="⑨入侵检测检测（IDS）"></a>⑨入侵检测检测（IDS）</h2><ul><li><p>主机入侵检测检测（HIDS）–主机侧</p></li><li><p>网络入侵检测系统（NIDS）–流量侧</p></li></ul><h2 id="⑩入侵防御系统（IPS）"><a href="#⑩入侵防御系统（IPS）" class="headerlink" title="⑩入侵防御系统（IPS）"></a>⑩入侵防御系统（IPS）</h2><ul><li>主机入侵防御系统（HIPS）</li><li>网络入侵防御系统（NIPS）</li></ul><h2 id="⑪Web应用防火墙（WAF）"><a href="#⑪Web应用防火墙（WAF）" class="headerlink" title="⑪Web应用防火墙（WAF）"></a>⑪Web应用防火墙（WAF）</h2><h2 id="⑫API安全"><a href="#⑫API安全" class="headerlink" title="⑫API安全"></a>⑫API安全</h2><ul><li><a href="https://www.dbappsecurity.com.cn/product/cloud2795.html">API风险监测系统（AiAAS ）_数据安全_安恒信息（上市公司） (dbappsecurity.com.cn)</a></li></ul><h2 id="⑬零信任"><a href="#⑬零信任" class="headerlink" title="⑬零信任"></a>⑬零信任</h2><ul><li><a href="https://e.huawei.com/cn/solutions/enterprise-network/security/hisec-zero-trust">智能零信任安全解决方案-华为企业业务</a></li><li><a href="https://www.h3c.com/cn/Products_And_Solution/Proactive_Security/Solutions/H3C_Trust/">零信任安全解决方案-新华三集团-H3C</a></li></ul><h2 id="⑭数据加密"><a href="#⑭数据加密" class="headerlink" title="⑭数据加密"></a>⑭数据加密</h2><ul><li><a href="https://www.h3c.com/cn/Products_And_Solution/Proactive_Security/Product_Series/Data_Security/DE2000/DE2000/DE2000/">H3C SecPath DE2000系列数据库加密与访问控制系统-新华三集团-H3C</a></li></ul><h2 id="统一身份认证（IAM）"><a href="#统一身份认证（IAM）" class="headerlink" title="统一身份认证（IAM）"></a>统一身份认证（IAM）</h2><p><a href="https://www.ibm.com/cn-zh/topics/identity-access-management">什么是身份和访问管理？ IAM、SSO、MFA 和 IDaaS 定义 | IBM</a></p><p>认证、授权和SSO是三个不同的概念。认证关注访问者身份是否合法，授权用于解决访问内容控制而SSO则用来改善登录多个服务时的用户体验。</p><p>认证：authentication，授权：authorization，SSO：Single sign-on。</p><p><a href="https://cloud.tencent.com/developer/article/1921741">一文读懂认证、授权和SSO，顺便了解一下IAM-腾讯云开发者社区-腾讯云 (tencent.com)</a></p><h2 id="扩展检测与响应（XDR）"><a href="#扩展检测与响应（XDR）" class="headerlink" title="扩展检测与响应（XDR）"></a>扩展检测与响应（XDR）</h2><h2 id="安全编排自动化与响应（SOAR）"><a href="#安全编排自动化与响应（SOAR）" class="headerlink" title="安全编排自动化与响应（SOAR）"></a>安全编排自动化与响应（SOAR）</h2><p>XDR NDR</p><h1 id="📚参考资料"><a href="#📚参考资料" class="headerlink" title="📚参考资料"></a>📚参考资料</h1><ul><li><a href="https://mp.weixin.qq.com/s/I7JZ2iAk6oYJpbErLXQYjg?poc_token=HKuXymajGc8PKkmPq-xfMT88uwkqJAZfL9Pf_oXf">你以为你以为的EDR就是EDR嘛？ (qq.com)</a></li><li><a href="https://mp.weixin.qq.com/s/98pNdMnmumIo3buJFfqgRA">常见网络安全设备简析——终端安全检测与响应（EDR） (qq.com)</a></li><li><a href="https://mp.weixin.qq.com/s/YfipnROsAncQpThyO_zjWQ">入侵与攻击模拟（BAS）国内产品的模仿、创新与未来发展路径 (qq.com)</a></li><li><a href="https://mp.weixin.qq.com/s/GEQk4y86G9y3_W-y5fR33A">入侵和攻击模拟（BAS）技术应用实践及热门产品分析 (qq.com)</a></li></ul><h1 id="❤️Sponsor"><a href="#❤️Sponsor" class="headerlink" title="❤️Sponsor"></a>❤️Sponsor</h1><p>您的支持是我不断前进的动力，如果您感觉本文对您有所帮助的话，可以考虑打赏一下本文，用以维持本博客的运营费用，拒绝白嫖，从你我做起！🥰🥰🥰</p><table>  <tbody>     <tr>         <td style="text-align:center;">支付宝</td>         <td style="text-align:center;">微信</td>     </tr>   <tr>    <td style="text-align:center;" ><img width="200" src="https://jwt1399.top/medias/reward/alipay.png"></td>          <td style="text-align:center;"><img width="200" src="https://jwt1399.top/medias/reward/sponor_wechat.png"></td>       </tr></tbody></table>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;h2 id=&quot;⓪数据安全定义&quot;&gt;&lt;a href=&quot;#⓪数据安全定义&quot; class=&quot;headerlink&quot;</summary>
        
      
    
    
    
    <category term="Data" scheme="https://jwt1399.top/categories/Data/"/>
    
    
    <category term="Security" scheme="https://jwt1399.top/tags/Security/"/>
    
  </entry>
  
  <entry>
    <title>大数据-Hadoop</title>
    <link href="https://jwt1399.top/posts/49020.html"/>
    <id>https://jwt1399.top/posts/49020.html</id>
    <published>2024-07-01T08:33:21.000Z</published>
    <updated>2024-07-09T11:47:35.810Z</updated>
    
    <content type="html"><![CDATA[<h2 id="前置知识"><a href="#前置知识" class="headerlink" title="前置知识"></a>前置知识</h2><p>大数据三个阶段</p><ul><li>阶段 1：Hadoop 生态体系</li><li>阶段 2：Spark 离线计算</li><li>阶段 3：Flink 实时计算</li></ul><p>大数据两个方向</p><ul><li>离线开发方向——Hadoop、Spark</li><li>实时计算方向——Flink、Hbase</li></ul><p>大数据核心工作</p><ul><li>分布式数据存储——<code>HDFS</code>、HBase、KuDu</li><li>分布式数据计算——MapReduce、<code>Hive</code>、<code>Spark</code>、<code>Flink</code></li><li>分布式数据传输——<code>KafKa</code>、Pulsar、Flume、Sqoop</li></ul><p>Hadoop 三大功能组件</p><ul><li>数据存储——HDFS</li><li>数据计算——MapReduce</li><li>资源调度——YARN</li></ul><p>为什么需要分布式存储？</p><ul><li><p>数据量太大，单机存储能力有上限，需要靠数量来解决问题</p></li><li><p>数量的提升带来的是网络传输、磁盘读写、CPU、内存等各方面的综合提升。 </p></li><li><p>分布式组合在一起可以达到1+1&gt;2的效果</p></li></ul><p>分布式系统常见的组织形式？</p><ul><li><p>去中心化模式：没有明确中心，大家协调工作</p></li><li><p>中心化模式：有明确的中心，基于中心节点分配工作</p></li></ul><p>Hadoop是哪种模式？</p><ul><li>主从模式（中心化模式）的架构</li></ul><h2 id="HDFS的基础架构"><a href="#HDFS的基础架构" class="headerlink" title="HDFS的基础架构"></a>HDFS的基础架构</h2><blockquote><p>HDFS是Hadoop三大组件(HDFS、MapReduce、YARN)之一，全称是：Hadoop Distributed File System（Hadoop分布式文件系统），是Hadoop技术栈内提供的分布式数据存储解决方案，可以在多台服务器上构建存储集群，存储海量的数据。</p></blockquote><h3 id="HDFS集群角色"><a href="#HDFS集群角色" class="headerlink" title="HDFS集群角色"></a><strong>HDFS集群角色</strong></h3><ul><li><p>主角色：NameNode</p><ul><li>HDFS系统的主角色，是一个独立的进程</li><li>负责管理HDFS整个文件系统</li><li>负责管理DataNode</li></ul></li><li><p>从角色：DataNode</p><ul><li>HDFS系统的从角色，是一个独立进程</li><li>主要负责数据的存储，即存入数据和取出数据</li></ul></li><li><p>主角色辅助角色：SecondaryNameNode</p><ul><li>NameNode的辅助，是一个独立进程</li><li>主要帮助NameNode完成元数据整理工作（打杂）</li></ul></li></ul><img src="https://img.jwt1399.top/img/202404101154127.png" style="zoom:50%;" /><h3 id="Hadoop安装包目录结构"><a href="#Hadoop安装包目录结构" class="headerlink" title="Hadoop安装包目录结构"></a><strong>Hadoop安装包目录结构</strong></h3><ul><li><p>bin，存放Hadoop的各类程序（命令）</p></li><li><p>etc，存放Hadoop的配置文件</p></li><li><p>include，C语言的一些头文件</p></li><li><p>lib，存放Linux系统的动态链接库（.so文件）</p></li><li><p>libexec，存放配置Hadoop系统的脚本文件（.sh和.cmd）</p></li><li><p>licenses-binary，存放许可证文件</p></li><li><p>sbin，管理员程序（super bin）</p></li><li><p>share，存放二进制源码（Java jar包）</p></li></ul><h3 id="配置HDFS集群，涉及的文件的修改"><a href="#配置HDFS集群，涉及的文件的修改" class="headerlink" title="配置HDFS集群，涉及的文件的修改"></a><strong>配置HDFS集群，涉及的文件的修改</strong></h3><ul><li><p>workers： 配置从节点（DataNode）有哪些</p></li><li><p>hadoop-env.sh： 配置Hadoop的相关环境变量</p></li><li><p>core-site.xml： Hadoop核心配置文件</p></li><li><p>hdfs-site.xml： HDFS核心配置文件</p></li></ul><p>这些文件均存在与<code>$HADOOP_HOME/etc/hadoop</code>文件夹中。<code>$HADOOP_HOME</code>设置的环境变量，其指代Hadoop安装文件夹</p><h4 id="配置workers文件"><a href="#配置workers文件" class="headerlink" title="配置workers文件"></a><strong>配置workers文件</strong></h4><pre class="line-numbers language-bash"><code class="language-bash"><span class="token comment" spellcheck="true"># 进入配置文件目录</span><span class="token function">cd</span> etc/hadoop<span class="token comment" spellcheck="true"># 编辑workers文件</span>vim workers<span class="token comment" spellcheck="true"># 填入如下内容</span>node1node2node3<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="配置hadoop-env-sh文件"><a href="#配置hadoop-env-sh文件" class="headerlink" title="配置hadoop-env.sh文件"></a>配置hadoop-env.sh文件</h4><pre class="line-numbers language-bash"><code class="language-bash"><span class="token comment" spellcheck="true"># 填入如下内容</span><span class="token function">export</span> JAVA_HOME<span class="token operator">=</span>/export/server/jdk<span class="token function">export</span> HADOOP_HOME<span class="token operator">=</span>/export/server/hadoop<span class="token function">export</span> HADOOP_CONF_DIR<span class="token operator">=</span><span class="token variable">$HADOOP_HOME</span>/etc/hadoop<span class="token function">export</span> HADOOP_LOG_DIR<span class="token operator">=</span><span class="token variable">$HADOOP_HOME</span>/logs<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li><p>JAVA_HOME，指明JDK环境的位置在哪</p></li><li><p>HADOOP_HOME，指明Hadoop安装位置</p></li><li><p>HADOOP_CONF_DIR，指明Hadoop配置文件目录位置</p></li><li><p>HADOOP_LOG_DIR，指明Hadoop运行日志目录位置</p></li></ul><p>通过记录这些环境变量， 来指明上述运行时的重要信息</p><h4 id="配置core-site-xml文件"><a href="#配置core-site-xml文件" class="headerlink" title="配置core-site.xml文件"></a>配置core-site.xml文件</h4><pre class="line-numbers language-xml"><code class="language-xml">在文件内部填入如下内容<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>configuration</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>property</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>name</span><span class="token punctuation">></span></span>fs.defaultFS<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>name</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>value</span><span class="token punctuation">></span></span>hdfs://node1:8020<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>value</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>property</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>property</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>name</span><span class="token punctuation">></span></span>io.file.buffer.size<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>name</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>value</span><span class="token punctuation">></span></span>131072<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>value</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>property</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>configuration</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li><p><code>fs.defaultFS</code>：HDFS文件系统的网络通讯路径</p></li><li><p><code>hdfs://node1:8020</code>：协议为hdfs:&#x2F;&#x2F;，namenode为node1，namenode通讯端口为8020</p></li><li><p><code>io.file.buffer.size</code>：io操作文件缓冲区大小</p></li></ul><h4 id="配置hdfs-site-xml文件"><a href="#配置hdfs-site-xml文件" class="headerlink" title="配置hdfs-site.xml文件"></a>配置hdfs-site.xml文件</h4><pre class="line-numbers language-xml"><code class="language-xml"># 在文件内部填入如下内容<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>configuration</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>property</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>name</span><span class="token punctuation">></span></span>dfs.datanode.data.dir.perm<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>name</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>value</span><span class="token punctuation">></span></span>700<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>value</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>property</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>property</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>name</span><span class="token punctuation">></span></span>dfs.namenode.name.dir<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>name</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>value</span><span class="token punctuation">></span></span>/data/nn<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>value</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>property</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>property</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>name</span><span class="token punctuation">></span></span>dfs.namenode.hosts<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>name</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>value</span><span class="token punctuation">></span></span>node1,node2,node3<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>value</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>property</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>property</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>name</span><span class="token punctuation">></span></span>dfs.blocksize<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>name</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>value</span><span class="token punctuation">></span></span>268435456<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>value</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>property</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>property</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>name</span><span class="token punctuation">></span></span>dfs.namenode.handler.count<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>name</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>value</span><span class="token punctuation">></span></span>100<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>value</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>property</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>property</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>name</span><span class="token punctuation">></span></span>dfs.datanode.data.dir<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>name</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>value</span><span class="token punctuation">></span></span>/data/dn<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>value</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>property</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>configuration</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li><p><code>dfs.datanode.data.dir.perm</code>：hdfs文件系统，默认创建的文件权限设置。700，即：rwx——</p></li><li><p><code>dfs.namenode.name.dir</code>：NameNode元数据的存储位置。&#x2F;data&#x2F;nn，在node1节点的&#x2F;data&#x2F;nn目录下</p></li><li><p><code>dfs.namenode.hosts</code>：NameNode允许哪几个节点的DataNode连接（即允许加入集群）</p></li><li><p><code>dfs.blocksize</code>：hdfs默认块大小。268435456（256MB）</p></li><li><p><code>dfs.namenode.handler.count</code>：namenode处理的并发线程数。以100个并行度处理文件系统的管理任务</p></li><li><p><code>dfs.datanode.data.dir</code>：从节点DataNode的数据存储目录。&#x2F;data&#x2F;dn，即数据存放在node1、node2、node3，三台机器的&#x2F;data&#x2F;dn内</p></li></ul><h2 id="HDFS的Shell操作"><a href="#HDFS的Shell操作" class="headerlink" title="HDFS的Shell操作"></a>HDFS的Shell操作</h2><h3 id="进程启停管理"><a href="#进程启停管理" class="headerlink" title="进程启停管理"></a>进程启停管理</h3><p>Hadoop HDFS组件内置了HDFS集群的一键启停脚本。</p><pre class="line-numbers language-bash"><code class="language-bash"><span class="token comment" spellcheck="true">#一键启动HDFS集群</span><span class="token variable">$HADOOP_HOME</span>/sbin/start-dfs.sh<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>执行原理：</p><ul><li><p>在执行此脚本的机器上，启动SecondaryNameNode</p></li><li><p>读取core-site.xml内容（fs.defaultFS项），确认NameNode所在机器，启动NameNode</p></li><li><p>读取workers内容，确认DataNode所在机器，启动全部DataNode</p></li></ul><pre class="line-numbers language-bash"><code class="language-bash"><span class="token comment" spellcheck="true">#一键关闭HDFS集群</span><span class="token variable">$HADOOP_HOME</span>/sbin/stop-dfs.sh<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>执行原理：</p><ul><li><p>在执行此脚本的机器上，关闭SecondaryNameNode</p></li><li><p>读取core-site.xml内容（fs.defaultFS项），确认NameNode所在机器，关闭NameNode</p></li><li><p>读取workers内容，确认DataNode所在机器，关闭全部NameNode</p></li></ul><p>除了一键启停外，也可以单独控制进程的启停。</p><ol><li><code>$HADOOP_HOME/sbin/hadoop-daemon.sh</code>，此脚本可以单独控制所在机器的进程的启停</li></ol><p>用法：<code>hadoop-daemon.sh (start|status|stop) (namenode|secondarynamenode|datanode)</code></p><ol start="2"><li><code>$HADOOP_HOME/bin/hdfs</code>，此程序也可以用以单独控制所在机器的进程的启停</li></ol><p>用法：<code>hdfs --daemon (start|status|stop) (namenode|secondarynamenode|datanode)</code></p><h3 id="文件系统操作命令"><a href="#文件系统操作命令" class="headerlink" title="文件系统操作命令"></a>文件系统操作命令</h3><p>关于HDFS文件系统的操作命令，Hadoop提供了2套命令体系</p><ul><li><p>hadoop命令（老版本用法），用法：<code>hadoop fs [generic options]</code></p></li><li><p>hdfs命令（新版本用法），用法：<code>hdfs dfs [generic options]</code></p></li></ul><p>两者在文件系统操作上，用法完全一致，用哪个都可以，某些特殊操作需要选择hadoop命令或hdfs命令</p><p>常用命令：</p><ul><li><p>mkdir 创建文件夹</p></li><li><p>ls、cat 列出内容、查看内容</p></li><li><p>cp、mv、rmr 复制、移动、删除</p></li><li><p>put、get 上传、下载</p></li><li><p>appendToFile 向文件追加内容</p></li></ul><h4 id="1-创建文件夹"><a href="#1-创建文件夹" class="headerlink" title="1.创建文件夹"></a>1.创建文件夹</h4><pre class="line-numbers language-bash"><code class="language-bash">hadoop fs -mkdir <span class="token punctuation">[</span>-p<span class="token punctuation">]</span> <span class="token operator">&lt;</span>path<span class="token operator">></span> <span class="token punctuation">..</span>.hdfs dfs -mkdir <span class="token punctuation">[</span>-p<span class="token punctuation">]</span> <span class="token operator">&lt;</span>path<span class="token operator">></span> <span class="token punctuation">..</span>.<span class="token comment" spellcheck="true"># path 为待创建的目录</span><span class="token comment" spellcheck="true"># -p选项的行为与Linux mkdir -p一致，它会沿着路径创建父目录。</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><h4 id="2-查看指定目录下内容"><a href="#2-查看指定目录下内容" class="headerlink" title="2.查看指定目录下内容"></a>2.查看指定目录下内容</h4><pre class="line-numbers language-bash"><code class="language-bash">hadoop fs -ls <span class="token punctuation">[</span>-h<span class="token punctuation">]</span> <span class="token punctuation">[</span>-R<span class="token punctuation">]</span> <span class="token punctuation">[</span><span class="token operator">&lt;</span>path<span class="token operator">></span> <span class="token punctuation">..</span>.<span class="token punctuation">]</span> hdfs dfs -ls <span class="token punctuation">[</span>-h<span class="token punctuation">]</span> <span class="token punctuation">[</span>-R<span class="token punctuation">]</span> <span class="token punctuation">[</span><span class="token operator">&lt;</span>path<span class="token operator">></span> <span class="token punctuation">..</span>.<span class="token punctuation">]</span> <span class="token comment" spellcheck="true"># path 指定目录路径</span><span class="token comment" spellcheck="true"># -h 人性化显示文件size</span><span class="token comment" spellcheck="true"># -R 递归查看指定目录及其子目录</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="3-上传文件到HDFS指定目录下"><a href="#3-上传文件到HDFS指定目录下" class="headerlink" title="3.上传文件到HDFS指定目录下"></a>3.上传文件到HDFS指定目录下</h4><pre class="line-numbers language-bash"><code class="language-bash">hadoop fs -put <span class="token punctuation">[</span>-f<span class="token punctuation">]</span> <span class="token punctuation">[</span>-p<span class="token punctuation">]</span> <span class="token operator">&lt;</span>localsrc<span class="token operator">></span> <span class="token punctuation">..</span>. <span class="token operator">&lt;</span>dst<span class="token operator">></span>hdfs dfs -put <span class="token punctuation">[</span>-f<span class="token punctuation">]</span> <span class="token punctuation">[</span>-p<span class="token punctuation">]</span> <span class="token operator">&lt;</span>localsrc<span class="token operator">></span> <span class="token punctuation">..</span>. <span class="token operator">&lt;</span>dst<span class="token operator">></span><span class="token comment" spellcheck="true"># -f 覆盖目标文件（已存在下）</span><span class="token comment" spellcheck="true"># -p 保留访问和修改时间，所有权和权限。</span><span class="token comment" spellcheck="true"># localsrc 本地文件系统（客户端所在机器）</span><span class="token comment" spellcheck="true"># dst 目标文件系统（HDFS）</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="4-查看HDFS文件内容"><a href="#4-查看HDFS文件内容" class="headerlink" title="4.查看HDFS文件内容"></a>4.查看HDFS文件内容</h4><pre class="line-numbers language-bash"><code class="language-bash">hadoop fs -cat <span class="token operator">&lt;</span>src<span class="token operator">></span> <span class="token punctuation">..</span>. hdfs dfs -cat <span class="token operator">&lt;</span>src<span class="token operator">></span> <span class="token punctuation">..</span>.<span class="token comment" spellcheck="true">#读取指定文件全部内容，显示在标准输出控制台。</span><span class="token comment" spellcheck="true">#读取大文件可以使用管道符配合more</span>hadoop fs -cat <span class="token operator">&lt;</span>src<span class="token operator">></span> <span class="token operator">|</span> <span class="token function">more</span>hdfs dfs -cat <span class="token operator">&lt;</span>src<span class="token operator">></span> <span class="token operator">|</span> <span class="token function">more</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="5-下载HDFS文件"><a href="#5-下载HDFS文件" class="headerlink" title="5.下载HDFS文件"></a>5.下载HDFS文件</h4><pre class="line-numbers language-bash"><code class="language-bash">hadoop fs -get <span class="token punctuation">[</span>-f<span class="token punctuation">]</span> <span class="token punctuation">[</span>-p<span class="token punctuation">]</span> <span class="token operator">&lt;</span>src<span class="token operator">></span> <span class="token punctuation">..</span>. <span class="token operator">&lt;</span>localdst<span class="token operator">></span>hdfs dfs -get <span class="token punctuation">[</span>-f<span class="token punctuation">]</span> <span class="token punctuation">[</span>-p<span class="token punctuation">]</span> <span class="token operator">&lt;</span>src<span class="token operator">></span> <span class="token punctuation">..</span>. <span class="token operator">&lt;</span>localdst<span class="token operator">></span><span class="token comment" spellcheck="true">#下载文件到本地文件系统指定目录，localdst必须是目录</span><span class="token comment" spellcheck="true">#-f 覆盖目标文件（已存在下）</span><span class="token comment" spellcheck="true">#-p 保留访问和修改时间，所有权和权限。</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="6-拷贝HDFS文件"><a href="#6-拷贝HDFS文件" class="headerlink" title="6.拷贝HDFS文件"></a>6.拷贝HDFS文件</h4><pre class="line-numbers language-bash"><code class="language-bash">hadoop fs -cp <span class="token punctuation">[</span>-f<span class="token punctuation">]</span> <span class="token operator">&lt;</span>src<span class="token operator">></span> <span class="token punctuation">..</span>. <span class="token operator">&lt;</span>dst<span class="token operator">></span> hdfs dfs -cp <span class="token punctuation">[</span>-f<span class="token punctuation">]</span> <span class="token operator">&lt;</span>src<span class="token operator">></span> <span class="token punctuation">..</span>. <span class="token operator">&lt;</span>dst<span class="token operator">></span><span class="token comment" spellcheck="true"># -f 覆盖目标文件（已存在下）</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><h4 id="7-追加数据到HDFS文件中"><a href="#7-追加数据到HDFS文件中" class="headerlink" title="7.追加数据到HDFS文件中"></a>7.追加数据到HDFS文件中</h4><pre class="line-numbers language-bash"><code class="language-bash">hadoop fs -appendToFile <span class="token operator">&lt;</span>localsrc<span class="token operator">></span> <span class="token punctuation">..</span>. <span class="token operator">&lt;</span>dst<span class="token operator">></span>hdfs dfs -appendToFile <span class="token operator">&lt;</span>localsrc<span class="token operator">></span> <span class="token punctuation">..</span>. <span class="token operator">&lt;</span>dst<span class="token operator">></span><span class="token comment" spellcheck="true">#将所有给定本地文件的内容追加到给定dst文件。 </span><span class="token comment" spellcheck="true">#dst如果文件不存在，将创建该文件。 </span><span class="token comment" spellcheck="true">#如果&lt;localSrc>为-，则输入为从标准输入中读取。</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="8-HDFS数据移动操作"><a href="#8-HDFS数据移动操作" class="headerlink" title="8.HDFS数据移动操作"></a>8.HDFS数据移动操作</h4><pre class="line-numbers language-bash"><code class="language-bash">hadoop fs -mv <span class="token operator">&lt;</span>src<span class="token operator">></span> <span class="token punctuation">..</span>. <span class="token operator">&lt;</span>dst<span class="token operator">></span>hdfs dfs -mv <span class="token operator">&lt;</span>src<span class="token operator">></span> <span class="token punctuation">..</span>. <span class="token operator">&lt;</span>dst<span class="token operator">></span><span class="token comment" spellcheck="true"># 移动文件到指定文件夹下</span><span class="token comment" spellcheck="true"># 可以使用该命令移动数据，重命名文件的名称</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><h4 id="9-HDFS数据删除操作"><a href="#9-HDFS数据删除操作" class="headerlink" title="9.HDFS数据删除操作"></a>9.HDFS数据删除操作</h4><pre class="line-numbers language-bash"><code class="language-bash">hadoop fs -rm -r <span class="token punctuation">[</span>-skipTrash<span class="token punctuation">]</span> URI <span class="token punctuation">[</span>URI <span class="token punctuation">..</span>.<span class="token punctuation">]</span>hdfs dfs -rm -r <span class="token punctuation">[</span>-skipTrash<span class="token punctuation">]</span> URI <span class="token punctuation">[</span>URI <span class="token punctuation">..</span>.<span class="token punctuation">]</span><span class="token comment" spellcheck="true"># 删除指定路径的文件或文件夹</span><span class="token comment" spellcheck="true"># -skipTrash 跳过回收站，直接删除</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>命令官方指导文档：<a href="https://hadoop.apache.org/docs/r3.3.4/hadoop-project-dist/hadoop-common/FileSystemShell.html">Apache Hadoop 3.3.4 – Overview</a></p><h1 id="❤️Sponsor"><a href="#❤️Sponsor" class="headerlink" title="❤️Sponsor"></a>❤️Sponsor</h1><p>您的支持是我不断前进的动力，如果您感觉本文对您有所帮助的话，可以考虑打赏一下本文，用以维持本博客的运营费用，拒绝白嫖，从你我做起！🥰🥰🥰</p><table>  <tbody>     <tr>         <td style="text-align:center;">支付宝</td>         <td style="text-align:center;">微信</td>     </tr>   <tr>    <td style="text-align:center;" ><img width="200" src="https://jwt1399.top/medias/reward/alipay.png"></td>          <td style="text-align:center;"><img width="200" src="https://jwt1399.top/medias/reward/sponor_wechat.png"></td>       </tr></tbody></table>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;h2 id=&quot;前置知识&quot;&gt;&lt;a href=&quot;#前置知识&quot; class=&quot;headerlink&quot; title=&quot;前置知识&quot;&gt;&lt;/a&gt;前置知识&lt;/h2&gt;&lt;p&gt;大数据三个阶段&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;阶段 1：Hadoop 生态体系&lt;/li&gt;
&lt;li&gt;阶段 2：Spark</summary>
        
      
    
    
    
    <category term="JavaSE" scheme="https://jwt1399.top/categories/JavaSE/"/>
    
    
    <category term="大数据" scheme="https://jwt1399.top/tags/%E5%A4%A7%E6%95%B0%E6%8D%AE/"/>
    
  </entry>
  
  <entry>
    <title>秋招总结</title>
    <link href="https://jwt1399.top/posts/42537.html"/>
    <id>https://jwt1399.top/posts/42537.html</id>
    <published>2023-12-01T02:39:46.000Z</published>
    <updated>2025-11-05T01:54:39.808Z</updated>
    
    <content type="html"><![CDATA[<p>博客很久没更新了（不会等太久哈，后续会继续更新技术文章），很大一部分原因是因为小简即将毕业，每天忙于找工作和撰写毕业论文！目前工作也找得差不多了，是时候给大家分享哈我的秋招经历，仅供参考。</p><h2 id="个人情况"><a href="#个人情况" class="headerlink" title="个人情况"></a>个人情况</h2><p>本人普本，985 硕，主要投递岗位为安全工程师和 Java 开发，9 月初开始投递（因去参加 HVV 所以投递得比较晚），大约投递了 40 多家，目前（12 月初）收到 6-7 个 offer，放弃了 9-10 家面试，其余的不是挂了就是杳无音信，9 月中下旬收到第一个 offer（美团），后面陆续收到天翼云，腾讯，云智研发，龙湖数科，华为等 offer。</p><p><img src="https://img.jwt1399.top/img/202312021751149.jpg"></p><h2 id="校招准备"><a href="#校招准备" class="headerlink" title="校招准备"></a>校招准备</h2><ul><li>写好简历！推荐使用超级简历或者 MarkDown 编写，建议校招期间不断优化自己的简历。</li><li>熟悉技能！简历中涉及的技能以及项目需要滚瓜烂熟，面试官主要就是根据你的简历来进行提问。</li><li>刷算法题！如果你想去大厂，基础算法题必须得会，我个人是刷了接近 400 道力扣算法。</li><li>背八股文！计算机相关岗位多多少少都有自己的一些八股文，建议提早整理并烂熟于心。</li></ul><h2 id="校招经验"><a href="#校招经验" class="headerlink" title="校招经验"></a>校招经验</h2><ul><li>尽早投简历！越早投越好！不要认为自己没准备好就不投，等准备好再投已经晚了。</li><li>面完要复盘！最好每次面试结束后撰写面经，将不足之处整理并且进行加强。</li><li>回答要聪明！尽量把话题引向自己熟悉的地方，自我介绍时就要开始引导，不要被面试官牵着走。</li><li>心态要稳定！前期没有 offer 时大家都会陷入焦虑的情绪，会自我否定，要及时调节，坚持就是胜利。</li><li>多相互交流！推荐使用牛客网、脉脉以及各种秋招交流群，可以查看面经以及大家的面试进展。</li></ul><h2 id="offer选择"><a href="#offer选择" class="headerlink" title="offer选择"></a>offer选择</h2><p>个人认为优先考虑以下条件</p><ul><li>薪资、工作地、稳定性、个人提升空间</li><li>公司认可度、未来发展、上班时间</li><li>福利（五险一金，房补，餐补…）、培养机制</li></ul><p>我个人是比较期望去到大厂学习前沿技术，而不是找一个可以躺平的公司摆烂！希望以上内容可以帮助到大家，如果此刻你跟我处于同种境地，那么加油吧！与您共勉！</p><h1 id="❤️Sponsor"><a href="#❤️Sponsor" class="headerlink" title="❤️Sponsor"></a>❤️Sponsor</h1><p>您的支持是我不断前进的动力，如果您感觉本文对您有所帮助的话，可以考虑打赏一下本文，用以维持本博客的运营费用，拒绝白嫖，从你我做起！🥰🥰🥰</p><table>  <tbody>     <tr>         <td style="text-align:center;">支付宝</td>         <td style="text-align:center;">微信</td>     </tr>   <tr>    <td style="text-align:center;" ><img width="200" src="https://jwt1399.top/medias/reward/alipay.png"></td>          <td style="text-align:center;"><img width="200" src="https://jwt1399.top/medias/reward/sponor_wechat.png"></td>       </tr></tbody></table>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;p&gt;博客很久没更新了（不会等太久哈，后续会继续更新技术文章），很大一部分原因是因为小简即将毕业，每天忙于找工作和撰写毕业论文！目前工作也找得差不多了，是时候给大家分享哈我的秋招经历，仅供参考。&lt;/p&gt;
&lt;h2 id=&quot;个人情况&quot;&gt;&lt;a href=&quot;#个人情况&quot;</summary>
        
      
    
    
    
    <category term="Share" scheme="https://jwt1399.top/categories/Share/"/>
    
    
    <category term="总结" scheme="https://jwt1399.top/tags/%E6%80%BB%E7%BB%93/"/>
    
  </entry>
  
  <entry>
    <title>ZooKeeper</title>
    <link href="https://jwt1399.top/posts/7340.html"/>
    <id>https://jwt1399.top/posts/7340.html</id>
    <published>2023-03-07T11:42:36.000Z</published>
    <updated>2025-11-10T01:37:10.340Z</updated>
    
    <content type="html"><![CDATA[<h2 id="ZK简介"><a href="#ZK简介" class="headerlink" title="ZK简介"></a>ZK简介</h2><p>Zookeeper 是 Apache Hadoop 项目下的一个子项目，是一个树形目录服务。Zookeeper 是一个分布式的、开源的分布式应用程序的协调服务。</p><p>Zookeeper 提供的主要功能包括：配置管理、分布式锁、集群管理</p><h2 id="ZK安装"><a href="#ZK安装" class="headerlink" title="ZK安装"></a>ZK安装</h2><h3 id="下载"><a href="#下载" class="headerlink" title="下载"></a>下载</h3><p><a href="https://archive.apache.org/dist/zookeeper/?spm=a2c6h.12873639.article-detail.137.533f3225FXsqOd">Index of &#x2F;dist&#x2F;zookeeper (apache.org)</a></p><h3 id="配置"><a href="#配置" class="headerlink" title="配置"></a>配置</h3><p>配置zoo.cfg，进入到conf目录拷贝一个zoo_sample.cfg并完成配置</p><pre class="line-numbers language-shell"><code class="language-shell">#进入到conf目录cd apache-zooKeeper-3.5.6-bin/conf/#拷贝cp  zoo_sample.cfg  zoo.cfg<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>修改zoo.cfg</p><pre class="line-numbers language-shell"><code class="language-shell">vim apache-zooKeeper-3.5.6-bin/conf/zoo.cfg<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>修改存储目录：dataDir&#x3D;你自己的目录</p><p>启动ZooKeeper</p><pre class="line-numbers language-shell"><code class="language-shell">./zkServer.sh  start<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>查看ZooKeeper状态</p><pre class="line-numbers language-shell"><code class="language-shell">./zkServer.sh status<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>standalone代表zk没有搭建集群，现在是单节点</p><h2 id="数据结构"><a href="#数据结构" class="headerlink" title="数据结构"></a>数据结构</h2><p>ZooKeeper 是一个树形目录服务，其数据模型和Unix的文件系统目录树很类似，拥有一个层次化结构。</p><p>这里面的每一个节点都被称为： ZNode，每个节点上都会保存自己的数据和节点信息。</p><p>节点可以拥有子节点，同时也允许少量（1MB）数据存储在该节点之下。</p><p>节点可以分为四大类：</p><ul><li><p>PERSISTENT 持久化节点</p></li><li><p>EPHEMERAL 临时节点 ：-e</p></li><li><p>PERSISTENT_SEQUENTIAL 持久化顺序节点 ：-s</p></li><li><p>EPHEMERAL_SEQUENTIAL 临时顺序节点 ：-es</p></li></ul><h2 id="命令操作"><a href="#命令操作" class="headerlink" title="命令操作"></a>命令操作</h2><h3 id="服务端常用命令"><a href="#服务端常用命令" class="headerlink" title="服务端常用命令"></a>服务端常用命令</h3><ul><li><p>启动 ZooKeeper 服务: <code>./zkServer.sh start</code></p></li><li><p>查看 ZooKeeper 服务状态: <code>./zkServer.sh status</code></p></li><li><p>停止 ZooKeeper 服务: <code>./zkServer.sh stop</code> </p></li><li><p>重启 ZooKeeper 服务: <code>./zkServer.sh restart</code></p></li></ul><h3 id="客户端常用命令"><a href="#客户端常用命令" class="headerlink" title="客户端常用命令"></a>客户端常用命令</h3><blockquote><p>Zookeeper 可视化客户端推荐：<a href="http://www.redisant.cn/za">ZooKeeper Assistant </a>(收费)、<a href="https://github.com/vran-dev/PrettyZoo">PrettyZoo</a>（免费）</p></blockquote><ul><li><p>连接ZooKeeper服务端 <code>./zkCli.sh –server 服务端ip:port</code></p></li><li><p>断开连接 <code>quit</code></p></li><li><p>显示指定目录下节点  <code>ls 目录</code></p></li><li><p>创建节点 <code>create /节点path value</code></p><ul><li>创建临时节点 <code>create -e /节点path value</code></li><li>创建顺序节点 <code>create -s /节点path value</code></li></ul></li><li><p>获取节点值 <code>get /节点path</code></p></li><li><p>设置节点值 <code>set /节点path value</code></p></li><li><p>删除单个节点  <code>delete /节点path </code></p></li><li><p>删除带有子节点的节点 <code>deleteall /节点path </code></p></li><li><p>查询节点详细信息 <code>ls –s /节点path</code></p></li></ul><pre class="line-numbers language-properties"><code class="language-properties"><span class="token attr-name">czxid：节点被创建的事务ID</span> <span class="token attr-name">ctime</span><span class="token punctuation">:</span> <span class="token attr-value">创建时间 </span><span class="token attr-name">mzxid</span><span class="token punctuation">:</span> <span class="token attr-value">最后一次被更新的事务ID </span><span class="token attr-name">mtime</span><span class="token punctuation">:</span> <span class="token attr-value">修改时间 </span>pzxid：子节点列表最后一次被更新的事务ID<span class="token attr-name">cversion：子节点的版本号</span> <span class="token attr-name">dataversion：数据版本号</span> <span class="token attr-name">aclversion：权限版本号</span> <span class="token attr-name">ephemeralOwner：用于临时节点，代表临时节点的事务ID，如果为持久节点则为0</span> <span class="token attr-name">dataLength：节点存储的数据的长度</span> numChildren：当前节点的子节点个数<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="JavaAPI操作"><a href="#JavaAPI操作" class="headerlink" title="JavaAPI操作"></a>JavaAPI操作</h2><h3 id="引入-Curator"><a href="#引入-Curator" class="headerlink" title="引入 Curator"></a>引入 Curator</h3><p>Apache Curator 是 ZooKeeper（分布式协调服务）的 Java&#x2F;JVM 客户端库。它包括一个高级API框架和实用程序，使使用Apache ZooKeeper变得更加容易和可靠。</p><ul><li><p>Curator 项目的目标是简化 ZooKeeper 客户端的使用。</p></li><li><p>Curator 最初是 Netfix 研发的,后来捐献了 Apache 基金会,目前是 Apache 的顶级项目。</p></li><li><p>官网：<a href="http://curator.apache.org/">http://curator.apache.org/</a></p></li></ul><p>使用时引入依赖</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependencies</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.apache.curator<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>curator-recipes<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>5.5.0<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span>         <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.apache.curator<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>curator-framework<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>5.5.0<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span>                <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.apache.zookeeper<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>zookeeper<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>3.8.1<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>scope</span><span class="token punctuation">></span></span>test<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>scope</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependencies</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="测试环境"><a href="#测试环境" class="headerlink" title="测试环境"></a>测试环境</h3><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">CuratorTest</span> <span class="token punctuation">{</span>    <span class="token keyword">private</span> CuratorFramework client<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">/**     * 建立连接     */</span>    <span class="token annotation punctuation">@Before</span>  <span class="token comment" spellcheck="true">//在所有 @test方法之前执行</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">testConnect</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">//重试策略</span>        RetryPolicy retryPolicy <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ExponentialBackoffRetry</span><span class="token punctuation">(</span><span class="token number">3000</span><span class="token punctuation">,</span> <span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        client <span class="token operator">=</span> CuratorFrameworkFactory<span class="token punctuation">.</span><span class="token function">builder</span><span class="token punctuation">(</span><span class="token punctuation">)</span>                <span class="token punctuation">.</span><span class="token function">connectString</span><span class="token punctuation">(</span><span class="token string">"127.0.0.1:2181"</span><span class="token punctuation">)</span>                <span class="token punctuation">.</span><span class="token function">sessionTimeoutMs</span><span class="token punctuation">(</span><span class="token number">60</span> <span class="token operator">*</span> <span class="token number">1000</span><span class="token punctuation">)</span>                <span class="token punctuation">.</span><span class="token function">connectionTimeoutMs</span><span class="token punctuation">(</span><span class="token number">15</span> <span class="token operator">*</span> <span class="token number">1000</span><span class="token punctuation">)</span>                <span class="token punctuation">.</span><span class="token function">retryPolicy</span><span class="token punctuation">(</span>retryPolicy<span class="token punctuation">)</span>                <span class="token punctuation">.</span><span class="token function">namespace</span><span class="token punctuation">(</span><span class="token string">"jwt"</span><span class="token punctuation">)</span>                <span class="token punctuation">.</span><span class="token function">build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">//开启连接</span>        client<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>      <span class="token comment" spellcheck="true">// 此处写测试代码</span>      <span class="token annotation punctuation">@Test</span>        <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">testXXX</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>      <span class="token comment" spellcheck="true">//。。。。</span>     <span class="token comment" spellcheck="true">// 建立连接、添加节点、删除节点、修改节点、查询节点</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@After</span>  <span class="token comment" spellcheck="true">//在所有 @test方法之后执行</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>client <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>            client<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>   <span class="token punctuation">}</span>  <span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="建立连接"><a href="#建立连接" class="headerlink" title="建立连接"></a>建立连接</h3><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">//1.第一种方式</span><span class="token annotation punctuation">@Test</span><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">testConnect</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>  <span class="token comment" spellcheck="true">/*   *   * @param connectString       连接字符串。zk server 地址和端口 "127.0.0.1:2181,"   * @param sessionTimeoutMs    会话超时时间 单位ms   * @param connectionTimeoutMs 连接超时时间 单位ms   * @param retryPolicy         重试策略   */</span>  <span class="token comment" spellcheck="true">//重试策略</span>  RetryPolicy retryPolicy <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ExponentialBackoffRetry</span><span class="token punctuation">(</span><span class="token number">3000</span><span class="token punctuation">,</span><span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  CuratorFramework client <span class="token operator">=</span> CuratorFrameworkFactory<span class="token punctuation">.</span><span class="token function">newClient</span><span class="token punctuation">(</span><span class="token string">"127.0.0.1:2181"</span><span class="token punctuation">,</span>          <span class="token number">60</span> <span class="token operator">*</span> <span class="token number">1000</span><span class="token punctuation">,</span> <span class="token number">15</span> <span class="token operator">*</span> <span class="token number">1000</span><span class="token punctuation">,</span> retryPolicy<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment" spellcheck="true">//开启连接</span>  client<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">//2.第二种方式</span><span class="token annotation punctuation">@Test</span><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">testConnect_02</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>      <span class="token comment" spellcheck="true">//重试策略</span>      RetryPolicy retryPolicy <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ExponentialBackoffRetry</span><span class="token punctuation">(</span><span class="token number">3000</span><span class="token punctuation">,</span> <span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      CuratorFramework client <span class="token operator">=</span> CuratorFrameworkFactory<span class="token punctuation">.</span><span class="token function">builder</span><span class="token punctuation">(</span><span class="token punctuation">)</span>              <span class="token punctuation">.</span><span class="token function">connectString</span><span class="token punctuation">(</span><span class="token string">"127.0.0.1:2181"</span><span class="token punctuation">)</span>              <span class="token punctuation">.</span><span class="token function">sessionTimeoutMs</span><span class="token punctuation">(</span><span class="token number">60</span> <span class="token operator">*</span> <span class="token number">1000</span><span class="token punctuation">)</span>              <span class="token punctuation">.</span><span class="token function">connectionTimeoutMs</span><span class="token punctuation">(</span><span class="token number">15</span> <span class="token operator">*</span> <span class="token number">1000</span><span class="token punctuation">)</span>              <span class="token punctuation">.</span><span class="token function">retryPolicy</span><span class="token punctuation">(</span>retryPolicy<span class="token punctuation">)</span>              <span class="token punctuation">.</span><span class="token function">namespace</span><span class="token punctuation">(</span><span class="token string">"jwt"</span><span class="token punctuation">)</span>              <span class="token punctuation">.</span><span class="token function">build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token comment" spellcheck="true">//开启连接</span>      client<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>namespace 命名空间，每次操作都在该空间目录下</p><h3 id="添加节点"><a href="#添加节点" class="headerlink" title="添加节点"></a>添加节点</h3><p>创建节点：create 持久 临时 顺序 数据</p><ul><li><ol><li>基本创建 ：create().forPath(“”)</li></ol></li><li><ol start="2"><li>创建节点 带有数据:create().forPath(“”,data)</li></ol></li><li><ol start="3"><li>设置节点的类型：create().withMode().forPath(“”,data)</li></ol></li><li><ol start="4"><li>创建多级节点  &#x2F;app1&#x2F;p1 ：create().creatingParentsIfNeeded().forPath(“”,data)</li></ol></li></ul><pre class="line-numbers language-java"><code class="language-java">    <span class="token annotation punctuation">@Test</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">testCreate</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">throws</span> Exception <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">//2. 创建节点 带有数据</span>        <span class="token comment" spellcheck="true">//如果创建节点，没有指定数据，则默认将当前客户端的ip作为数据存储</span>        String path <span class="token operator">=</span> client<span class="token punctuation">.</span><span class="token function">create</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">forPath</span><span class="token punctuation">(</span><span class="token string">"/app2"</span><span class="token punctuation">,</span> <span class="token string">"hehe"</span><span class="token punctuation">.</span><span class="token function">getBytes</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>path<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@Test</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">testCreate2</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">throws</span> Exception <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">//1. 基本创建</span>        <span class="token comment" spellcheck="true">//如果创建节点，没有指定数据，则默认将当前客户端的ip作为数据存储</span>        String path <span class="token operator">=</span> client<span class="token punctuation">.</span><span class="token function">create</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">forPath</span><span class="token punctuation">(</span><span class="token string">"/app1"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>path<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@Test</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">testCreate3</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">throws</span> Exception <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">//3. 设置节点的类型</span>        <span class="token comment" spellcheck="true">//默认类型：持久化</span>        String path <span class="token operator">=</span> client<span class="token punctuation">.</span><span class="token function">create</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">withMode</span><span class="token punctuation">(</span>CreateMode<span class="token punctuation">.</span>EPHEMERAL<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">forPath</span><span class="token punctuation">(</span><span class="token string">"/app3"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>path<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@Test</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">testCreate4</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">throws</span> Exception <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">//4. 创建多级节点  /app1/p1</span>        <span class="token comment" spellcheck="true">//creatingParentsIfNeeded():如果父节点不存在，则创建父节点</span>        String path <span class="token operator">=</span> client<span class="token punctuation">.</span><span class="token function">create</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">creatingParentsIfNeeded</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">forPath</span><span class="token punctuation">(</span><span class="token string">"/app4/p1"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>path<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="查询节点"><a href="#查询节点" class="headerlink" title="查询节点"></a>查询节点</h3><ul><li><ol><li>查询数据：get: getData().forPath()</li></ol></li><li><ol start="2"><li>查询子节点： ls: getChildren().forPath()</li></ol></li><li><ol start="3"><li>查询节点状态信息：ls -s:getData().storingStatIn(状态对象).forPath()</li></ol></li></ul><pre class="line-numbers language-java"><code class="language-java">    <span class="token annotation punctuation">@Test</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">testGet1</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">throws</span> Exception <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">//1. 查询数据：get</span>        <span class="token keyword">byte</span><span class="token punctuation">[</span><span class="token punctuation">]</span> data <span class="token operator">=</span> client<span class="token punctuation">.</span><span class="token function">getData</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">forPath</span><span class="token punctuation">(</span><span class="token string">"/app1"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">String</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@Test</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">testGet2</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">throws</span> Exception <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 2. 查询子节点： ls</span>        List<span class="token operator">&lt;</span>String<span class="token operator">></span> path <span class="token operator">=</span> client<span class="token punctuation">.</span><span class="token function">getChildren</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">forPath</span><span class="token punctuation">(</span><span class="token string">"/"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>path<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@Test</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">testGet3</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">throws</span> Exception <span class="token punctuation">{</span>        Stat status <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Stat</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>status<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">//3. 查询节点状态信息：ls -s</span>        client<span class="token punctuation">.</span><span class="token function">getData</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">storingStatIn</span><span class="token punctuation">(</span>status<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">forPath</span><span class="token punctuation">(</span><span class="token string">"/app1"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>status<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="修改节点"><a href="#修改节点" class="headerlink" title="修改节点"></a>修改节点</h3><ul><li>1.基本修改数据：setData().forPath()</li><li>2.根据版本修改: setData().withVersion().forPath()</li></ul><p>version 是通过查询出来的。目的就是为了让其他客户端或者线程不干扰我。</p><pre class="line-numbers language-java"><code class="language-java">    <span class="token annotation punctuation">@Test</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">testSet</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">throws</span> Exception <span class="token punctuation">{</span>        client<span class="token punctuation">.</span><span class="token function">setData</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">forPath</span><span class="token punctuation">(</span><span class="token string">"/app1"</span><span class="token punctuation">,</span> <span class="token string">"hello"</span><span class="token punctuation">.</span><span class="token function">getBytes</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@Test</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">testSetForVersion</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">throws</span> Exception <span class="token punctuation">{</span>        Stat status <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Stat</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">//3. 查询节点状态信息：ls -s</span>        client<span class="token punctuation">.</span><span class="token function">getData</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">storingStatIn</span><span class="token punctuation">(</span>status<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">forPath</span><span class="token punctuation">(</span><span class="token string">"/app1"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">int</span> version <span class="token operator">=</span> status<span class="token punctuation">.</span><span class="token function">getVersion</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//查询出来的 </span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>version<span class="token punctuation">)</span><span class="token punctuation">;</span>        client<span class="token punctuation">.</span><span class="token function">setData</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">withVersion</span><span class="token punctuation">(</span>version<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">forPath</span><span class="token punctuation">(</span><span class="token string">"/app1"</span><span class="token punctuation">,</span> <span class="token string">"hehe"</span><span class="token punctuation">.</span><span class="token function">getBytes</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="删除节点"><a href="#删除节点" class="headerlink" title="删除节点"></a>删除节点</h3><p>删除节点： delete deleteall</p><ul><li><ol><li>删除单个节点:delete().forPath(“&#x2F;app1”);</li></ol></li><li>2 . 删除带有子节点的节点:delete().deletingChildrenIfNeeded().forPath(“&#x2F;app1”);</li><li><ol start="3"><li>必须成功的删除:为了防止网络抖动。本质就是重试。  client.delete().guaranteed().forPath(“&#x2F;app2”);</li></ol></li><li>4.回调：inBackground</li></ul><pre class="line-numbers language-java"><code class="language-java">    <span class="token annotation punctuation">@Test</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">testDelete</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">throws</span> Exception <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 1. 删除单个节点</span>        client<span class="token punctuation">.</span><span class="token function">delete</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">forPath</span><span class="token punctuation">(</span><span class="token string">"/app1"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@Test</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">testDelete2</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">throws</span> Exception <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">//2. 删除带有子节点的节点</span>        client<span class="token punctuation">.</span><span class="token function">delete</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">deletingChildrenIfNeeded</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">forPath</span><span class="token punctuation">(</span><span class="token string">"/app4"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@Test</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">testDelete3</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">throws</span> Exception <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">//3. 必须成功的删除</span>        client<span class="token punctuation">.</span><span class="token function">delete</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">guaranteed</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">forPath</span><span class="token punctuation">(</span><span class="token string">"/app2"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@Test</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">testDelete4</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">throws</span> Exception <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">//4. 回调</span>        client<span class="token punctuation">.</span><span class="token function">delete</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">guaranteed</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">inBackground</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">BackgroundCallback</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>            <span class="token annotation punctuation">@Override</span>            <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">processResult</span><span class="token punctuation">(</span>CuratorFramework client<span class="token punctuation">,</span> CuratorEvent event<span class="token punctuation">)</span> <span class="token keyword">throws</span> Exception <span class="token punctuation">{</span>                System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"我被删除了~"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>event<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">forPath</span><span class="token punctuation">(</span><span class="token string">"/app1"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="Watch事件监听"><a href="#Watch事件监听" class="headerlink" title="Watch事件监听"></a>Watch事件监听</h2><p>ZooKeeper 允许用户在指定节点上注册一些Watcher，并且在一些特定事件触发的时候，ZooKeeper 服务端会将事件通知到感兴趣的客户端上去，该机制是 ZooKeeper 实现分布式协调服务的重要特性。</p><p>ZooKeeper 中引入了Watcher机制来实现了发布&#x2F;订阅功能能，能够让多个订阅者同时监听某一个对象，当一个对象自身状态变化时，会通知所有订阅者。</p><p>ZooKeeper 原生支持通过注册Watcher来进行事件监听，但是其使用并不是特别方便，需要开发人员自己反复注册Watcher，比较繁琐。<strong>Curator</strong> 引入了 Cache 来实现对 ZooKeeper 服务端事件的监听。</p><p>ZooKeeper提供了三种Watcher：</p><ul><li><p>NodeCache : 只是监听某一个特定的节点</p></li><li><p>PathChildrenCache : 监控一个ZNode的子节点. </p></li><li><p>TreeCache : 可以监控整个节点自己和所有的子节点，类似于PathChildrenCache和NodeCache的组合</p></li></ul><h3 id="测试环境-1"><a href="#测试环境-1" class="headerlink" title="测试环境"></a>测试环境</h3><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">CuratorWatcherTest</span> <span class="token punctuation">{</span>    <span class="token keyword">private</span> CuratorFramework client<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">/**     * 建立连接     */</span>    <span class="token annotation punctuation">@Before</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">testConnect</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">//重试策略</span>        RetryPolicy retryPolicy <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ExponentialBackoffRetry</span><span class="token punctuation">(</span><span class="token number">3000</span><span class="token punctuation">,</span> <span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">//2.第二种方式</span>        client <span class="token operator">=</span> CuratorFrameworkFactory<span class="token punctuation">.</span><span class="token function">builder</span><span class="token punctuation">(</span><span class="token punctuation">)</span>                <span class="token punctuation">.</span><span class="token function">connectString</span><span class="token punctuation">(</span><span class="token string">"127.0.0.1:2181"</span><span class="token punctuation">)</span>                <span class="token punctuation">.</span><span class="token function">sessionTimeoutMs</span><span class="token punctuation">(</span><span class="token number">60</span> <span class="token operator">*</span> <span class="token number">1000</span><span class="token punctuation">)</span>                <span class="token punctuation">.</span><span class="token function">connectionTimeoutMs</span><span class="token punctuation">(</span><span class="token number">15</span> <span class="token operator">*</span> <span class="token number">1000</span><span class="token punctuation">)</span>                <span class="token punctuation">.</span><span class="token function">retryPolicy</span><span class="token punctuation">(</span>retryPolicy<span class="token punctuation">)</span>                <span class="token punctuation">.</span><span class="token function">namespace</span><span class="token punctuation">(</span><span class="token string">"jwt"</span><span class="token punctuation">)</span>                <span class="token punctuation">.</span><span class="token function">build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">//开启连接</span>        client<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>      <span class="token comment" spellcheck="true">// 测试代码写此处。。。。</span>         <span class="token comment" spellcheck="true">/**     * 关闭连接     */</span>    <span class="token annotation punctuation">@After</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>client <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>            client<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="NodeCache"><a href="#NodeCache" class="headerlink" title="NodeCache"></a>NodeCache</h3><p> 只是监听某一个特定的节点</p><pre class="line-numbers language-java"><code class="language-java">    <span class="token comment" spellcheck="true">/**     * 演示 NodeCache：给指定一个节点注册监听器（监听节点/jwt/app1）     */</span>    <span class="token annotation punctuation">@Test</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">testNodeCache</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">throws</span> Exception <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">//1. 创建NodeCache对象</span>        <span class="token keyword">final</span> NodeCache nodeCache <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">NodeCache</span><span class="token punctuation">(</span>client<span class="token punctuation">,</span><span class="token string">"/app1"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">//2. 注册监听</span>        nodeCache<span class="token punctuation">.</span><span class="token function">getListenable</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">addListener</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">NodeCacheListener</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token annotation punctuation">@Override</span>            <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">nodeChanged</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">throws</span> Exception <span class="token punctuation">{</span>                System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"节点变化了~"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token comment" spellcheck="true">//获取修改节点后的数据</span>                <span class="token keyword">byte</span><span class="token punctuation">[</span><span class="token punctuation">]</span> data <span class="token operator">=</span> nodeCache<span class="token punctuation">.</span><span class="token function">getCurrentData</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getData</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">String</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">//3. 开启监听.如果设置为true，则开启监听是，加载缓冲数据</span>        nodeCache<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 测试需要，保证监听器一直运行</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="PathChildrenCache"><a href="#PathChildrenCache" class="headerlink" title="PathChildrenCache"></a>PathChildrenCache</h3><p> 监控一个ZNode的子节点</p><pre class="line-numbers language-java"><code class="language-java">    <span class="token comment" spellcheck="true">/**     * 演示 PathChildrenCache：监听某个节点的所有子节点们（监听/jwt/app2的子节点）     */</span>    <span class="token annotation punctuation">@Test</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">testPathChildrenCache</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">throws</span> Exception <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">//1.创建监听对象</span>        PathChildrenCache pathChildrenCache <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">PathChildrenCache</span><span class="token punctuation">(</span>client<span class="token punctuation">,</span><span class="token string">"/app2"</span><span class="token punctuation">,</span><span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">//2. 绑定监听器</span>        pathChildrenCache<span class="token punctuation">.</span><span class="token function">getListenable</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">addListener</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">PathChildrenCacheListener</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token annotation punctuation">@Override</span>            <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">childEvent</span><span class="token punctuation">(</span>CuratorFramework client<span class="token punctuation">,</span> PathChildrenCacheEvent event<span class="token punctuation">)</span> <span class="token keyword">throws</span> Exception <span class="token punctuation">{</span>                System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"子节点变化了~"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>event<span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token comment" spellcheck="true">//监听子节点的数据变更，并且拿到变更后的数据</span>                <span class="token comment" spellcheck="true">//1.获取类型</span>                PathChildrenCacheEvent<span class="token punctuation">.</span>Type type <span class="token operator">=</span> event<span class="token punctuation">.</span><span class="token function">getType</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token comment" spellcheck="true">//2.判断类型是否是update</span>                <span class="token keyword">if</span><span class="token punctuation">(</span>type<span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span>PathChildrenCacheEvent<span class="token punctuation">.</span>Type<span class="token punctuation">.</span>CHILD_UPDATED<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">{</span>                    System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"数据变了！！！"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                    <span class="token keyword">byte</span><span class="token punctuation">[</span><span class="token punctuation">]</span> data <span class="token operator">=</span> event<span class="token punctuation">.</span><span class="token function">getData</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getData</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                    System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">String</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token punctuation">}</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">//3. 开启</span>        pathChildrenCache<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">{</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="TreeCache"><a href="#TreeCache" class="headerlink" title="TreeCache"></a>TreeCache</h3><p>可以监控整个节点自己和所有的子节点，类似于PathChildrenCache和NodeCache的组合</p><pre class="line-numbers language-java"><code class="language-java">    <span class="token comment" spellcheck="true">/**     * 演示 TreeCache：监听某个节点自己和所有子节点们（监听/jwt/app2 和其子节点）     */</span>    <span class="token annotation punctuation">@Test</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">testTreeCache</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">throws</span> Exception <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">//1. 创建监听器</span>        TreeCache treeCache <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">TreeCache</span><span class="token punctuation">(</span>client<span class="token punctuation">,</span><span class="token string">"/app2"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">//2. 注册监听</span>        treeCache<span class="token punctuation">.</span><span class="token function">getListenable</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">addListener</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">TreeCacheListener</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token annotation punctuation">@Override</span>            <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">childEvent</span><span class="token punctuation">(</span>CuratorFramework client<span class="token punctuation">,</span> TreeCacheEvent event<span class="token punctuation">)</span> <span class="token keyword">throws</span> Exception <span class="token punctuation">{</span>                System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"节点变化了"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>event<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">//3. 开启</span>        treeCache<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">{</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="分布式锁"><a href="#分布式锁" class="headerlink" title="分布式锁"></a>分布式锁</h2><h3 id="ZK-分布式锁原理"><a href="#ZK-分布式锁原理" class="headerlink" title="ZK 分布式锁原理"></a>ZK 分布式锁原理</h3><blockquote><p>在我们进行单机应用开发，涉及并发同步的时候，我们往往采用synchronized或者Lock的方式来解决多线程间的代码同步问题，这时多线程的运行都是在同一个JVM之下，没有任何问题。但当我们的应用是分布式集群工作的情况下，属于多JVM下的工作环境，跨JVM之间已经无法通过多线程的锁解决同步问题。</p><p>那么就需要一种更加高级的锁机制，来处理种跨机器的进程之间的数据同步问题——这就是分布式锁。</p></blockquote><p>核心思想：当客户端要获取锁，则创建节点，使用完锁，则删除该节点。</p><ul><li><p>1.客户端获取锁时，在某节点(lock)下创建<code>临时顺序</code>节点。</p><ul><li>使用临时节点的原因：防止客户端宕机，而无法释放锁，如果使用临时节点，即使宕机，节点也会自动删除（临时节点特性，客户端断开连接，临时节点自动删除）</li><li>使用顺序节点的原因：因为需要找最小节点，需要排顺序</li></ul></li><li><p>2.然后获取该节点(lock)下面的所有子节点(&#x2F;lock&#x2F;1，&#x2F;lock&#x2F;2，&#x2F;lock&#x2F;3)，客户端获取到所有的子节点之后，如果发现自己创建的子节点序号最小，那么就认为该客户端获取到了锁。使用完锁后，将该节点删除。</p></li><li><p>3.如果发现自己创建的节点并非该节点所有子节点中最小的，说明自己还没有获取到锁，此时客户端需要找到比自己小的那个节点，同时对其注册事件监听器，监听删除事件。</p></li><li><p>4.如果发现比自己小的那个节点被删除，则客户端的监听器Watcher会收到相应通知，此时再次判断自己创建的节点是否是lock子节点中序号最小的，如果是则获取到了锁，如果不是则重复以上步骤继续获取到比自己小的一个节点，并注册监听。</p></li></ul><img src="https://img.jwt1399.top/img/202307222127328.png" style="zoom:50%;" /><p>总结：多个客户端在一个节点下创建<code>临时顺序</code>节点，然后去找序号最小的节点就是锁，用完之后删除就可以了。</p><p>在Curator中有五种锁方案：</p><ul><li><p>InterProcessSemaphoreMutex：分布式排它锁（非可重入锁）</p></li><li><p>InterProcessMutex：分布式可重入排它锁</p></li><li><p>InterProcessReadWriteLock：分布式读写锁</p></li><li><p>InterProcessMultiLock：将多个锁作为单个实体管理的容器</p></li><li><p>InterProcessSemaphoreV2：共享信号量</p></li></ul><h3 id="分布式锁案例"><a href="#分布式锁案例" class="headerlink" title="分布式锁案例"></a>分布式锁案例</h3><p>模拟12306售票</p><img src="https://img.jwt1399.top/img/202307231935613.png"  style="zoom:50%;" /><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">LockTest</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>        Ticket12306 ticket12306 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Ticket12306</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">//创建客户端</span>        Thread t1 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span>ticket12306<span class="token punctuation">,</span><span class="token string">"携程"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        Thread t2 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span>ticket12306<span class="token punctuation">,</span><span class="token string">"飞猪"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>          Thread t3 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span>ticket12306<span class="token punctuation">,</span><span class="token string">"去哪儿"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        t1<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        t2<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        t3<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Ticket12306</span> <span class="token keyword">implements</span> <span class="token class-name">Runnable</span><span class="token punctuation">{</span>    <span class="token keyword">private</span> <span class="token keyword">int</span> tickets <span class="token operator">=</span> <span class="token number">10</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//数据库的票数</span>    <span class="token keyword">private</span> InterProcessMutex lock <span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 构造函数</span>    <span class="token keyword">public</span> <span class="token function">Ticket12306</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 连接 ZK</span>        RetryPolicy retryPolicy <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ExponentialBackoffRetry</span><span class="token punctuation">(</span><span class="token number">3000</span><span class="token punctuation">,</span> <span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//重试策略</span>        CuratorFramework client <span class="token operator">=</span> CuratorFrameworkFactory<span class="token punctuation">.</span><span class="token function">builder</span><span class="token punctuation">(</span><span class="token punctuation">)</span>                <span class="token punctuation">.</span><span class="token function">connectString</span><span class="token punctuation">(</span><span class="token string">"127.0.0.1:2181"</span><span class="token punctuation">)</span>                <span class="token punctuation">.</span><span class="token function">sessionTimeoutMs</span><span class="token punctuation">(</span><span class="token number">60</span> <span class="token operator">*</span> <span class="token number">1000</span><span class="token punctuation">)</span>                <span class="token punctuation">.</span><span class="token function">connectionTimeoutMs</span><span class="token punctuation">(</span><span class="token number">15</span> <span class="token operator">*</span> <span class="token number">1000</span><span class="token punctuation">)</span>                <span class="token punctuation">.</span><span class="token function">retryPolicy</span><span class="token punctuation">(</span>retryPolicy<span class="token punctuation">)</span>                <span class="token punctuation">.</span><span class="token function">build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        client<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//开启连接</span>        <span class="token comment" spellcheck="true">// 创建锁</span>        lock <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">InterProcessMutex</span><span class="token punctuation">(</span>client<span class="token punctuation">,</span><span class="token string">"/lock"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">run</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">while</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">//获取锁</span>            <span class="token keyword">try</span> <span class="token punctuation">{</span>                lock<span class="token punctuation">.</span><span class="token function">acquire</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">,</span> TimeUnit<span class="token punctuation">.</span>SECONDS<span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token keyword">if</span><span class="token punctuation">(</span>tickets <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">{</span>                    System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">+</span><span class="token string">":"</span><span class="token operator">+</span>tickets<span class="token punctuation">)</span><span class="token punctuation">;</span>                    Thread<span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">100</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                    tickets<span class="token operator">--</span><span class="token punctuation">;</span>                <span class="token punctuation">}</span>            <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Exception</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span>                e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span><span class="token keyword">finally</span> <span class="token punctuation">{</span>                <span class="token comment" spellcheck="true">//释放锁</span>                <span class="token keyword">try</span> <span class="token punctuation">{</span>                    lock<span class="token punctuation">.</span><span class="token function">release</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Exception</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span>                    e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token punctuation">}</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="集群"><a href="#集群" class="headerlink" title="集群"></a>集群</h2><h3 id="集群搭建"><a href="#集群搭建" class="headerlink" title="集群搭建"></a>集群搭建</h3><h4 id="1-1-搭建要求"><a href="#1-1-搭建要求" class="headerlink" title="1.1 搭建要求"></a><strong>1.1 搭建要求</strong></h4><p>真实的集群是需要部署在不同的服务器上的，但是在我们测试时同时启动很多个虚拟机内存会吃不消，所以我们通常会搭建<strong>伪集群</strong>，也就是把所有的服务都搭建在一台虚拟机上，用端口进行区分。</p><p>我们这里要求搭建一个三个节点的Zookeeper集群（伪集群）。</p><h4 id="1-2-准备工作"><a href="#1-2-准备工作" class="headerlink" title="1.2 准备工作"></a><strong>1.2 准备工作</strong></h4><p>重新部署一台虚拟机作为我们搭建集群的测试服务器。</p><p>（1）安装JDK  【此步骤省略】。</p><p>（2）Zookeeper压缩包上传到服务器</p><p>（3）将Zookeeper解压 ，建立&#x2F;usr&#x2F;local&#x2F;zookeeper-cluster目录，将解压后的Zookeeper复制到以下三个目录</p><ul><li>&#x2F;usr&#x2F;local&#x2F;zookeeper-cluster&#x2F;zookeeper-1</li><li>&#x2F;usr&#x2F;local&#x2F;zookeeper-cluster&#x2F;zookeeper-2</li><li>&#x2F;usr&#x2F;local&#x2F;zookeeper-cluster&#x2F;zookeeper-3</li></ul><pre class="line-numbers language-shell"><code class="language-shell">[root@localhost ~]# mkdir /usr/local/zookeeper-cluster[root@localhost ~]# cp -r  apache-zookeeper-3.5.6-bin /usr/local/zookeeper-cluster/zookeeper-1[root@localhost ~]# cp -r  apache-zookeeper-3.5.6-bin /usr/local/zookeeper-cluster/zookeeper-2[root@localhost ~]# cp -r  apache-zookeeper-3.5.6-bin /usr/local/zookeeper-cluster/zookeeper-3<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>（4）创建data目录 ，并且将 conf下zoo_sample.cfg 文件改名为 zoo.cfg</p><pre class="line-numbers language-shell"><code class="language-shell">mkdir /usr/local/zookeeper-cluster/zookeeper-1/datamkdir /usr/local/zookeeper-cluster/zookeeper-2/datamkdir /usr/local/zookeeper-cluster/zookeeper-3/datamv  /usr/local/zookeeper-cluster/zookeeper-1/conf/zoo_sample.cfg  /usr/local/zookeeper-cluster/zookeeper-1/conf/zoo.cfgmv  /usr/local/zookeeper-cluster/zookeeper-2/conf/zoo_sample.cfg  /usr/local/zookeeper-cluster/zookeeper-2/conf/zoo.cfgmv  /usr/local/zookeeper-cluster/zookeeper-3/conf/zoo_sample.cfg  /usr/local/zookeeper-cluster/zookeeper-3/conf/zoo.cfg<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>（5） 配置每一个Zookeeper 的dataDir 和 clientPort 分别为2181  2182  2183</p><p>修改&#x2F;usr&#x2F;local&#x2F;zookeeper-cluster&#x2F;zookeeper-1&#x2F;conf&#x2F;zoo.cfg</p><pre class="line-numbers language-shell"><code class="language-shell">vim /usr/local/zookeeper-cluster/zookeeper-1/conf/zoo.cfgclientPort=2181dataDir=/usr/local/zookeeper-cluster/zookeeper-1/data<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>修改&#x2F;usr&#x2F;local&#x2F;zookeeper-cluster&#x2F;zookeeper-2&#x2F;conf&#x2F;zoo.cfg</p><pre class="line-numbers language-shell"><code class="language-shell">vim /usr/local/zookeeper-cluster/zookeeper-2/conf/zoo.cfgclientPort=2182dataDir=/usr/local/zookeeper-cluster/zookeeper-2/data<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>修改&#x2F;usr&#x2F;local&#x2F;zookeeper-cluster&#x2F;zookeeper-3&#x2F;conf&#x2F;zoo.cfg</p><pre class="line-numbers language-shell"><code class="language-shell">vim /usr/local/zookeeper-cluster/zookeeper-3/conf/zoo.cfgclientPort=2183dataDir=/usr/local/zookeeper-cluster/zookeeper-3/data<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><h4 id="1-3-配置集群"><a href="#1-3-配置集群" class="headerlink" title="1.3 配置集群"></a><strong>1.3 配置集群</strong></h4><p>（1）在每个zookeeper的 data 目录下创建一个 myid 文件，内容分别是1、2、3 。这个文件就是记录每个服务器的ID</p><pre class="line-numbers language-shell"><code class="language-shell">echo 1 >/usr/local/zookeeper-cluster/zookeeper-1/data/myidecho 2 >/usr/local/zookeeper-cluster/zookeeper-2/data/myidecho 3 >/usr/local/zookeeper-cluster/zookeeper-3/data/myid<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>（2）在每一个zookeeper 的 zoo.cfg配置客户端访问端口（clientPort）和集群服务器IP列表。</p><p>集群服务器IP列表如下</p><pre class="line-numbers language-shell"><code class="language-shell">vim /usr/local/zookeeper-cluster/zookeeper-1/conf/zoo.cfgvim /usr/local/zookeeper-cluster/zookeeper-2/conf/zoo.cfgvim /usr/local/zookeeper-cluster/zookeeper-3/conf/zoo.cfgserver.1=127.0.0.1:2881:3881server.2=127.0.0.1:2882:3882server.3=127.0.0.1:2883:3883<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>解释：server.服务器ID&#x3D;服务器IP地址：服务器之间通信端口：服务器之间投票选举端口</p><h4 id="1-4-启动集群"><a href="#1-4-启动集群" class="headerlink" title="1.4 启动集群"></a><strong>1.4 启动集群</strong></h4><p>启动集群就是分别启动每个实例。</p><pre class="line-numbers language-shell"><code class="language-shell">/usr/local/zookeeper-cluster/zookeeper-1/bin/zkServer.sh start/usr/local/zookeeper-cluster/zookeeper-2/bin/zkServer.sh start/usr/local/zookeeper-cluster/zookeeper-3/bin/zkServer.sh start <span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>启动后我们查询一下每个实例的运行状态</p><pre class="line-numbers language-shell"><code class="language-shell">/usr/local/zookeeper-cluster/zookeeper-1/bin/zkServer.sh status/usr/local/zookeeper-cluster/zookeeper-2/bin/zkServer.sh status/usr/local/zookeeper-cluster/zookeeper-3/bin/zkServer.sh status<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>先查询第一个服务</p><p>Mode为follower表示是<strong>跟随者</strong>（从）</p><p>再查询第二个服务Mod 为leader表示是<strong>领导者</strong>（主）</p><p>查询第三个为<strong>跟随者</strong>（从）</p><h4 id="1-5-模拟集群异常"><a href="#1-5-模拟集群异常" class="headerlink" title="1.5 模拟集群异常"></a><strong>1.5 模拟集群异常</strong></h4><p>（1）首先我们先测试如果是从服务器挂掉，会怎么样</p><p>把3号服务器停掉，观察1号和2号，发现状态并没有变化</p><pre class="line-numbers language-shell"><code class="language-shell">/usr/local/zookeeper-cluster/zookeeper-3/bin/zkServer.sh stop/usr/local/zookeeper-cluster/zookeeper-1/bin/zkServer.sh status/usr/local/zookeeper-cluster/zookeeper-2/bin/zkServer.sh status <span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>由此得出结论，3个节点的集群，从服务器挂掉，集群正常</p><p>（2）我们再把1号服务器（从服务器）也停掉，查看2号（主服务器）的状态，发现已经停止运行了。</p><pre class="line-numbers language-shell"><code class="language-shell">/usr/local/zookeeper-cluster/zookeeper-1/bin/zkServer.sh stop/usr/local/zookeeper-cluster/zookeeper-2/bin/zkServer.sh status<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>由此得出结论，3个节点的集群，2个从服务器都挂掉，主服务器也无法运行。因为可运行的机器没有超过集群总数量的半数。</p><p>（3）我们再次把1号服务器启动起来，发现2号服务器又开始正常工作了。而且依然是领导者。</p><pre class="line-numbers language-shell"><code class="language-shell">/usr/local/zookeeper-cluster/zookeeper-1/bin/zkServer.sh start/usr/local/zookeeper-cluster/zookeeper-2/bin/zkServer.sh status <span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>（4）我们把3号服务器也启动起来，把2号服务器停掉,停掉后观察1号和3号的状态。</p><pre class="line-numbers language-shell"><code class="language-shell">/usr/local/zookeeper-cluster/zookeeper-3/bin/zkServer.sh start/usr/local/zookeeper-cluster/zookeeper-2/bin/zkServer.sh stop/usr/local/zookeeper-cluster/zookeeper-1/bin/zkServer.sh status/usr/local/zookeeper-cluster/zookeeper-3/bin/zkServer.sh status<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>发现新的leader产生了~  </p><p>由此我们得出结论，当集群中的主服务器挂了，集群中的其他服务器会自动进行选举状态，然后产生新得leader </p><p>（5）我们再次测试，当我们把2号服务器重新启动起来启动后，会发生什么？2号服务器会再次成为新的领导吗？我们看结果</p><pre class="line-numbers language-shell"><code class="language-shell">/usr/local/zookeeper-cluster/zookeeper-2/bin/zkServer.sh start/usr/local/zookeeper-cluster/zookeeper-2/bin/zkServer.sh status/usr/local/zookeeper-cluster/zookeeper-3/bin/zkServer.sh status <span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>我们会发现，2号服务器启动后依然是跟随者（从服务器），3号服务器依然是领导者（主服务器），没有撼动3号服务器的领导地位。</p><p>由此我们得出结论，当领导者产生后，再次有新服务器加入集群，不会影响到现任领导者。</p><h3 id="集群角色"><a href="#集群角色" class="headerlink" title="集群角色"></a>集群角色</h3><p>ZooKeeper 中没有选择传统的 Master&#x2F;Slave 概念，而是引入了 Leader、Follower 和 Observer 三种角色。如下图所示</p><img src="https://img.jwt1399.top/img/202307231400587.png" style="zoom:75%;" /><table><thead><tr><th>角色</th><th>说明</th></tr></thead><tbody><tr><td>Leader</td><td>处理客户端事务请求(读写&#x2F;增删改查)，负责投票的发起和决议，更新系统状态。</td></tr><tr><td>Follower</td><td>处理客户端非事务请求(读&#x2F;查)，如果是写服务则转发给 Leader。参与Leader选举投票。</td></tr><tr><td>Observer</td><td>处理客户端非事务请求(读&#x2F;查)，如果是写服务则转发给 Leader。不参与Leader选举投票，也不参与“过半写成功”策略。在不影响写性能的情况下提升集群的读性能。此角色于 ZooKeeper3.3 系列新增的角色。</td></tr></tbody></table><p>ZooKeeper 集群中的所有机器通过一个 <strong>Leader 选举过程</strong> 来选定一台称为 “<strong>Leader</strong>” 的机器，Leader 既可以为客户端提供写服务又能提供读服务。除了 Leader 外，<strong>Follower</strong> 和 <strong>Observer</strong> 都只能提供读服务。Follower 和 Observer 唯一的区别在于 Observer 机器不参与 Leader 的选举过程，也不参与写操作的“过半写成功”策略，因此 Observer 机器<strong>可以在不影响写性能的情况下提升集群的读性能</strong>。</p><h3 id="Leader-选举"><a href="#Leader-选举" class="headerlink" title="Leader 选举"></a>Leader 选举</h3><p>Leader选举规则：</p><ul><li><p>Serverid：服务器ID</p><ul><li><p>比如有三台服务器，编号分别是1,2,3。</p></li><li><p>编号越大在选择算法中的权重越大。</p></li></ul></li><li><p>Zxid：数据ID</p><ul><li><p>服务器中存放的最大数据ID。值越大说明数据越新，在选举算法中数据越新权重越大。</p></li><li><p>在Leader选举的过程中，如果某台 ZooKeeper 获得了超过半数的选票，则此ZooKeeper就可以成为Leader</p></li></ul></li></ul><blockquote><p>当 Leader 服务器出现网络中断、崩溃退出与重启等异常情况时，就会进入 Leader 选举过程，这个过程会选举产生新的 Leader 服务器。</p></blockquote><p>Leader产生过程：</p><ol><li><strong>Leader election（选举阶段）</strong>：节点在一开始都处于选举阶段，只要有一个节点得到超半数节点的票数，它就可以当选准 leader。</li><li><strong>Discovery（发现阶段）</strong>：在这个阶段，followers 跟准 leader 进行通信，同步 followers 最近接收的事务提议。</li><li><strong>Synchronization（同步阶段）</strong>：同步阶段主要是利用 leader 前一阶段获得的最新提议历史，同步集群中所有的副本。同步完成之后准 leader 才会成为真正的 leader。</li><li><strong>Broadcast（广播阶段）</strong>：到了这个阶段，ZooKeeper 集群才能正式对外提供事务服务，并且 leader 可以进行消息广播。同时如果有新的节点加入，还需要对新节点进行同步。</li></ol><p>ZooKeeper 集群中的服务器状态有下面几种：</p><ul><li><strong>LOOKING</strong>：寻找 Leader。</li><li><strong>LEADING</strong>：Leader 状态，对应的节点为 Leader。</li><li><strong>FOLLOWING</strong>：Follower 状态，对应的节点为 Follower。</li><li><strong>OBSERVING</strong>：Observer 状态，对应节点为 Observer，该节点不参与 Leader 选举。</li></ul><h2 id="ZAB-协议"><a href="#ZAB-协议" class="headerlink" title="ZAB 协议"></a>ZAB 协议</h2><p>Paxos算法：一种基于消息传递且具有高度容错特性的一致性算法。</p><p>Paxos算法解决的问题：快速正确的在一个分布式系统中对某个数据值达成一致，并且保证不论发生任何异常，都不会破坏整个系统的一致性。</p><p>Zab 借鉴了 Paxos 算法，特别为 Zookeeper 设计的支持崩溃恢复的原子广播协议。基于该协议，Zookeeper 设计为只有一台客户端(Leader)负责处理外部的写事务请求，然后Leader 客户端将数据同步到其他 Follower 节点。即 Zookeeper 只有一个 Leader 可以发起提案。</p><p>ZAB 协议两种基本的模式：崩溃恢复和消息广播</p><p><strong>崩溃恢复</strong>：当整个服务框架在启动过程中，或是当 Leader 服务器出现网络中断、崩溃退出与重启等异常情况时，ZAB 协议就会进入恢复模式并选举产生新的 Leader 服务器。当选举产生了新的 Leader 服务器，同时集群中已经有过半的机器与该 Leader 服务器完成了状态同步之后，ZAB 协议就会退出恢复模式。其中，<strong>所谓的状态同步是指数据同步，用来保证集群中存在过半的机器能够和 Leader 服务器的数据状态保持一致</strong>。</p><p><strong>消息广播</strong>：<strong>当集群中已经有过半的 Follower 服务器完成了和 Leader 服务器的状态同步，那么整个服务框架就可以进入消息广播模式了。</strong> 当一台同样遵守 ZAB 协议的服务器启动后加入到集群中时，如果此时集群中已经存在一个 Leader 服务器在负责进行消息广播，那么新加入的服务器就会自觉地进入数据恢复模式：找到 Leader 所在的服务器，并与其进行数据同步，然后一起参与到消息广播流程中去。</p><h1 id="📚参考资料"><a href="#📚参考资料" class="headerlink" title="📚参考资料"></a>📚参考资料</h1><ul><li><a href="https://www.bilibili.com/video/BV1M741137qY">Zookeeper</a></li><li></li></ul><h1 id="❤️Sponsor"><a href="#❤️Sponsor" class="headerlink" title="❤️Sponsor"></a>❤️Sponsor</h1><p>您的支持是我不断前进的动力，如果您感觉本文对您有所帮助的话，可以考虑打赏一下本文，用以维持本博客的运营费用，拒绝白嫖，从你我做起！🥰🥰🥰</p><table>  <tbody>     <tr>         <td style="text-align:center;">支付宝</td>         <td style="text-align:center;">微信</td>     </tr>   <tr>    <td style="text-align:center;" ><img width="200" src="https://jwt1399.top/medias/reward/alipay.png"></td>          <td style="text-align:center;"><img width="200" src="https://jwt1399.top/medias/reward/sponor_wechat.png"></td>       </tr></tbody></table>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;h2 id=&quot;ZK简介&quot;&gt;&lt;a href=&quot;#ZK简介&quot; class=&quot;headerlink&quot; title=&quot;ZK简介&quot;&gt;&lt;/a&gt;ZK简介&lt;/h2&gt;&lt;p&gt;Zookeeper 是 Apache Hadoop 项目下的一个子项目，是一个树形目录服务。Zookeeper</summary>
        
      
    
    
    
    <category term="Spring" scheme="https://jwt1399.top/categories/Spring/"/>
    
    
    <category term="ZooKeeper" scheme="https://jwt1399.top/tags/ZooKeeper/"/>
    
  </entry>
  
  <entry>
    <title>Dubbo</title>
    <link href="https://jwt1399.top/posts/37154.html"/>
    <id>https://jwt1399.top/posts/37154.html</id>
    <published>2023-03-05T06:38:48.000Z</published>
    <updated>2025-11-10T01:37:42.723Z</updated>
    
    <content type="html"><![CDATA[<h1 id="架构演变"><a href="#架构演变" class="headerlink" title="架构演变"></a>架构演变</h1><img src="https://img.jwt1399.top/img/202307201755344.png" style="zoom:50%;" /><h2 id="基础知识"><a href="#基础知识" class="headerlink" title="基础知识"></a>基础知识</h2><h3 id="性能指标"><a href="#性能指标" class="headerlink" title="性能指标"></a>性能指标</h3><ul><li><p><strong>响应时间</strong>：指执行一个请求从开始到最后收到响应数据所花费的总体时间。</p></li><li><p><strong>并发数</strong>：指系统同时能处理的请求数量。</p><ul><li><p><strong>并发连接数</strong>：指的是客户端向服务器发起请求，并建立了TCP连接。每秒钟服务器连接的总TCP数量</p></li><li><p><strong>请求数</strong>：也称为QPS(Query Per Second) 指每秒多少请求.</p></li><li><p><strong>并发用户数</strong>：单位时间内有多少用户</p></li></ul></li><li><p><strong>吞吐量</strong>：指单位时间内系统能处理的请求数量。</p><ul><li><p><strong>QPS</strong>：Query Per Second 每秒查询数。</p></li><li><p><strong>TPS</strong>：Transactions Per Second 每秒事务数。</p></li><li><p>一个事务是指一个客户机向服务器发送请求然后服务器做出反应的过程。</p></li></ul></li></ul><h3 id="架构目标"><a href="#架构目标" class="headerlink" title="架构目标"></a>架构目标</h3><ul><li><p><strong>高性能</strong>：提供快速的访问体验。</p></li><li><p><strong>高可用</strong>：网站服务一直可以正常访问。</p></li><li><p><strong>可伸缩</strong>：通过硬件增加&#x2F;减少，提高&#x2F;降低处理能力。</p></li><li><p><strong>高可扩展</strong>：系统间耦合低，方便通过新增&#x2F;移除方式，增加&#x2F;减少新的功能&#x2F;模块。</p></li><li><p><strong>安全性</strong>：提供网站安全访问和数据加密，安全存储等策略。</p></li><li><p><strong>敏捷性</strong>：随需应变，快速响应。</p></li></ul><h3 id="相关概念"><a href="#相关概念" class="headerlink" title="相关概念"></a>相关概念</h3><ul><li><p>集群：很多“人”一起 ，干一样的事。</p><ul><li>一个业务模块，部署在多台服务器上。</li></ul></li><li><p>分布式：很多“人”一起，干不一样的事。这些不一样的事，合起来是一件大事。</p><ul><li>一个大的业务系统，拆分为小的业务模块，分别部署在不同的机器上。</li></ul></li></ul><h2 id="单体架构"><a href="#单体架构" class="headerlink" title="单体架构"></a>单体架构</h2><img src="https://img.jwt1399.top/img/202307201751622.png" style="zoom:50%;" /><p>优点：</p><ul><li>简单：开发部署都很方便，小型项目首选</li></ul><p>缺点：</p><ul><li>项目启动慢、可靠性差、可伸缩性差、扩展性和可维护性差、性能低</li></ul><h2 id="垂直架构"><a href="#垂直架构" class="headerlink" title="垂直架构"></a>垂直架构</h2><blockquote><p>将单体架构中的多个模块拆分为多个独立的项目。形成多个独立的单体架构。</p></blockquote><img src="https://img.jwt1399.top/img/202307201751823.png" style="zoom:50%;" /><p>垂直架构存在的问题：重复功能太多（例如图中 E 功能）</p><h2 id="分布式架构"><a href="#分布式架构" class="headerlink" title="分布式架构"></a>分布式架构</h2><blockquote><p>在垂直架构的基础上，将公共业务模块抽取出来，作为独立的服务，供其他调用者消费，以实现服务的共享和重用。</p></blockquote><img src="https://img.jwt1399.top/img/202307201754676.png"  style="zoom:50%;" /><p>分布式架构存在的问题：服务提供方地址一旦产生变更，所有消费方都需要变更。</p><h2 id="SOA架构"><a href="#SOA架构" class="headerlink" title="SOA架构"></a>SOA架构</h2><blockquote><p>SOA：（Service-Oriented Architecture，面向服务的架构）是一个组件模型，它将应用程序的不同功能单元（称为服务）进行拆分，并通过这些服务之间定义良好的接口和契约联系起来。</p><p>ESB：(Enterparise Servce Bus) 企业服务总线，服务中介。主要是提供了一个服务于服务之间的交互。ESB 包含的功能如：负载均衡，流量控制，加密处理，服务的监控，异常处理，监控告急等等</p></blockquote><img src="https://img.jwt1399.top/img/202307201757675.png" style="zoom:50%;" /><h2 id="微服务架构"><a href="#微服务架构" class="headerlink" title="微服务架构"></a>微服务架构</h2><blockquote><p>微服务架构是在 SOA 上做的升华，微服务架构强调的一个重点是“业务需要彻底的组件化和服务化”，原有的单个业务系统会拆分为多个可以独立开发、设计、运行的小应用。这些小应用之间通过服务完成交互和集成。</p><p>微服务架构 &#x3D; 80%的SOA服务架构思想 + 100%的组件化架构思想 + 80%的领域建模思想</p></blockquote><img src="https://img.jwt1399.top/img/202307201759170.png"  style="zoom:50%;" /><p>特点：</p><ul><li><p>服务实现组件化：开发者可以自由选择开发技术。也不需要协调其他团队</p></li><li><p>服务之间交互一般使用REST API</p></li><li><p>去中心化：每个微服务有自己私有的数据库持久化业务数据</p></li><li><p>自动化部署：把应用拆分成为一个一个独立的单个服务，方便自动化部署、测试、运维</p></li></ul><h1 id="Dubbo-简介"><a href="#Dubbo-简介" class="headerlink" title="Dubbo 简介"></a>Dubbo 简介</h1><p>Apache Dubbo 是一款易用、高性能的 WEB 和 RPC 框架，同时为构建企业级微服务提供服务发现、流量治理、可观测、认证鉴权等能力、工具与最佳实践。</p><p><img src="https://cn.dubbo.apache.org/imgs/architecture.png"></p><p>Dubbo 作为一款微服务框架，最重要的是向用户提供跨进程的 RPC 远程调用能力。如上图所示，Dubbo 的服务消费者（Consumer）通过一系列的工作将请求发送给服务提供者（Provider）。</p><p>为了实现这样一个目标，Dubbo 引入了注册中心（Registry）组件，通过注册中心，服务消费者可以感知到服务提供者的连接方式，从而将请求发送给正确的服务提供者。</p><p><a href="https://cn.dubbo.apache.org/zh-cn/overview/mannual/java-sdk/reference-manual/config/annotation/#%E6%B3%A8%E8%A7%A3">Dubbo 相关注解</a>、<a href="https://cn.dubbo.apache.org/zh-cn/overview/mannual/java-sdk/reference-manual/config/properties/">注解配置项参考手册</a></p><h1 id="Dubbo-快速入门"><a href="#Dubbo-快速入门" class="headerlink" title="Dubbo 快速入门"></a>Dubbo 快速入门</h1><blockquote><p>参考：<a href="https://cn.dubbo.apache.org/zh-cn/overview/quickstart/java/spring-boot/#%E5%8A%A8%E6%89%8B%E5%AE%9E%E8%B7%B5%E4%BB%8E%E9%9B%B6%E4%BB%A3%E7%A0%81%E5%BC%80%E5%8F%91%E7%89%88">3 - Dubbo Spring Boot Starter 开发微服务应用 | Apache Dubbo</a></p></blockquote><h2 id="1-启动注册中心-ZooKeeper"><a href="#1-启动注册中心-ZooKeeper" class="headerlink" title="1.启动注册中心 ZooKeeper"></a>1.启动注册中心 ZooKeeper</h2><p>要启动 ZooKeeper，您需要一个配置文件。这是一个示例，在 <strong>conf&#x2F;zoo</strong> 中创建它.cfg：</p><pre class="line-numbers language-properties"><code class="language-properties"><span class="token attr-name">tickTime</span><span class="token punctuation">=</span><span class="token attr-value">2000</span><span class="token attr-name">dataDir</span><span class="token punctuation">=</span><span class="token attr-value">/Users/jianjian/JavaSoft/apache-zookeeper-3.8.1-bin/data/</span><span class="token attr-name">clientPort</span><span class="token punctuation">=</span><span class="token attr-value">2181</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>启动ZooKeeper：</p><pre class="line-numbers language-bash"><code class="language-bash">bin/zkServer.sh start<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><h2 id="2-初始化项目"><a href="#2-初始化项目" class="headerlink" title="2.初始化项目"></a>2.初始化项目</h2><p>请参考：<a href="https://cn.dubbo.apache.org/zh-cn/overview/quickstart/java/spring-boot/#2-%E5%88%9D%E5%A7%8B%E5%8C%96%E9%A1%B9%E7%9B%AE">Dubbo Spring Boot Starter 开发微服务应用-初始化项目</a></p><h2 id="3-添加-Maven-依赖"><a href="#3-添加-Maven-依赖" class="headerlink" title="3.添加 Maven 依赖"></a>3.添加 Maven 依赖</h2><p>在初始化完项目以后，我们需要先添加 Dubbo 相关的 maven 依赖。</p><p>对于多模块项目，首先需要在父项目的 <code>pom.xml</code> 里面配置依赖信息。</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>properties</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dubbo.version</span><span class="token punctuation">></span></span>3.2.0<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dubbo.version</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>spring-boot.version</span><span class="token punctuation">></span></span>2.7.8<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>spring-boot.version</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>maven.compiler.source</span><span class="token punctuation">></span></span>8<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>maven.compiler.source</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>maven.compiler.target</span><span class="token punctuation">></span></span>8<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>maven.compiler.target</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>project.build.sourceEncoding</span><span class="token punctuation">></span></span>UTF-8<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>project.build.sourceEncoding</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>properties</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependencyManagement</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependencies</span><span class="token punctuation">></span></span>            <span class="token comment" spellcheck="true">&lt;!-- Spring Boot --></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>                <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework.boot<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>                <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-boot-dependencies<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>                  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>${spring-boot.version}<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span>                <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>type</span><span class="token punctuation">></span></span>pom<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>type</span><span class="token punctuation">></span></span>                <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>scope</span><span class="token punctuation">></span></span>import<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>scope</span><span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span>            <span class="token comment" spellcheck="true">&lt;!-- Dubbo --></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>                <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.apache.dubbo<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>                <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>dubbo-bom<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>                <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>${dubbo.version}<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span>                <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>type</span><span class="token punctuation">></span></span>pom<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>type</span><span class="token punctuation">></span></span>                <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>scope</span><span class="token punctuation">></span></span>import<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>scope</span><span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>                <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.apache.dubbo<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>                <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>dubbo-dependencies-zookeeper-curator5<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>                <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>${dubbo.version}<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span>                <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>type</span><span class="token punctuation">></span></span>pom<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>type</span><span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependencies</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependencyManagement</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>build</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>pluginManagement</span><span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>plugins</span><span class="token punctuation">></span></span>                <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>plugin</span><span class="token punctuation">></span></span>                    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework.boot<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>                    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-boot-maven-plugin<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>                    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>${spring-boot.version}<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span>                <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>plugin</span><span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>plugins</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>pluginManagement</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>build</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>然后在 <code>dubbo-spring-boot-consumer</code> 和 <code>dubbo-spring-boot-provider</code> 两个模块 <code>pom.xml</code> 中进行具体依赖的配置。</p><p>编辑 <code>./dubbo-spring-boot-consumer/pom.xml</code> 和 <code>./dubbo-spring-boot-provider/pom.xml</code> 这两文件，都添加下列配置。</p><pre class="line-numbers language-xml"><code class="language-xml">    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependencies</span><span class="token punctuation">></span></span>        <span class="token comment" spellcheck="true">&lt;!-- interface --></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.example<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>dubbo-spring-boot-demo-interface<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>${project.parent.version}<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span>        <span class="token comment" spellcheck="true">&lt;!-- dubbo --></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.apache.dubbo<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>dubbo-spring-boot-starter<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.apache.dubbo<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>dubbo-dependencies-zookeeper-curator5<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>type</span><span class="token punctuation">></span></span>pom<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>type</span><span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>exclusions</span><span class="token punctuation">></span></span>                <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>exclusion</span><span class="token punctuation">></span></span>                    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>slf4j-reload4j<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>                    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.slf4j<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>                <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>exclusion</span><span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>exclusions</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span>        <span class="token comment" spellcheck="true">&lt;!-- spring boot starter --></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework.boot<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-boot-starter<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependencies</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>在这份配置中，定义了 dubbo 和 zookeeper（以及对应的连接器 curator）的依赖。</p><p>添加了上述的配置以后，可以通过 IDEA 刷新依赖</p><h2 id="4-定义服务接口"><a href="#4-定义服务接口" class="headerlink" title="4.定义服务接口"></a>4.定义服务接口</h2><p>服务接口沟通消费端和服务端的桥梁。</p><p>在 <code>dubbo-spring-boot-demo-interface</code> 模块的 <code>org.apache.dubbo.springboot.demo</code> 下建立 <code>DemoService</code> 接口，定义如下：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">package</span> org<span class="token punctuation">.</span>apache<span class="token punctuation">.</span>dubbo<span class="token punctuation">.</span>springboot<span class="token punctuation">.</span>demo<span class="token punctuation">;</span><span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">DemoService</span> <span class="token punctuation">{</span>    String <span class="token function">sayHello</span><span class="token punctuation">(</span>String name<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>在 <code>DemoService</code> 中，定义了 <code>sayHello</code> 这个方法。后续服务端发布的服务，消费端订阅的服务都是围绕着 <code>DemoService</code> 接口展开的。</p><h2 id="5-定义服务端的实现"><a href="#5-定义服务端的实现" class="headerlink" title="5. 定义服务端的实现"></a>5. 定义服务端的实现</h2><p>定义了服务接口之后，可以在服务端这一侧定义对应的实现，这部分的实现相对于消费端来说是远端的实现，本地没有相关的信息。</p><p>在<code>dubbo-spring-boot-demo-provider</code> 模块的 <code>org.apache.dubbo.springboot.demo.provider</code> 下建立 <code>DemoServiceImpl</code> 类，定义如下：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">package</span> org<span class="token punctuation">.</span>apache<span class="token punctuation">.</span>dubbo<span class="token punctuation">.</span>springboot<span class="token punctuation">.</span>demo<span class="token punctuation">.</span>provider<span class="token punctuation">;</span><span class="token keyword">import</span> org<span class="token punctuation">.</span>apache<span class="token punctuation">.</span>dubbo<span class="token punctuation">.</span>config<span class="token punctuation">.</span>annotation<span class="token punctuation">.</span>DubboService<span class="token punctuation">;</span><span class="token keyword">import</span> org<span class="token punctuation">.</span>apache<span class="token punctuation">.</span>dubbo<span class="token punctuation">.</span>springboot<span class="token punctuation">.</span>demo<span class="token punctuation">.</span>DemoService<span class="token punctuation">;</span><span class="token annotation punctuation">@DubboService</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">DemoServiceImpl</span> <span class="token keyword">implements</span> <span class="token class-name">DemoService</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> String <span class="token function">sayHello</span><span class="token punctuation">(</span>String name<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">return</span> <span class="token string">"Hello "</span> <span class="token operator">+</span> name<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>在 <code>DemoServiceImpl</code> 中，实现了 <code>DemoService</code> 接口，对于 <code>sayHello</code> 方法返回 <code>Hello name</code>。</p><p>注：在<code>DemoServiceImpl</code> 类中添加了 <code>@DubboService</code> 注解，通过这个配置可以基于 Spring Boot 去发布 Dubbo 服务。</p><h2 id="6-配置服务端-YAML-配置文件"><a href="#6-配置服务端-YAML-配置文件" class="headerlink" title="6.配置服务端 YAML 配置文件"></a>6.配置服务端 YAML 配置文件</h2><p>从本步骤开始至第 7 步，将会通过 Spring Boot 的方式配置 Dubbo 的一些基础信息。</p><p>首先，我们先创建服务端的配置文件。</p><p>在 <code>dubbo-spring-boot-demo-provider</code> 模块的 <code>resources</code> 资源文件夹下建立 <code>application.yml</code> 文件，定义如下：</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">dubbo</span><span class="token punctuation">:</span>  <span class="token key atrule">application</span><span class="token punctuation">:</span>    <span class="token key atrule">name</span><span class="token punctuation">:</span> dubbo<span class="token punctuation">-</span>springboot<span class="token punctuation">-</span>demo<span class="token punctuation">-</span>provider  <span class="token key atrule">protocol</span><span class="token punctuation">:</span>    <span class="token key atrule">name</span><span class="token punctuation">:</span> dubbo    <span class="token key atrule">port</span><span class="token punctuation">:</span> <span class="token number">-1</span>  <span class="token key atrule">registry</span><span class="token punctuation">:</span>    <span class="token key atrule">address</span><span class="token punctuation">:</span> zookeeper<span class="token punctuation">:</span>//localhost<span class="token punctuation">:</span><span class="token number">2181</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>在这个配置文件中，定义了 Dubbo 的应用名、Dubbo 协议信息、Dubbo 使用的注册中心地址。</p><h2 id="7-配置消费端-YAML-配置文件"><a href="#7-配置消费端-YAML-配置文件" class="headerlink" title="7. 配置消费端 YAML 配置文件"></a>7. 配置消费端 YAML 配置文件</h2><p>同样的，我们需要创建消费端的配置文件。</p><p>在 <code>dubbo-spring-boot-demo-consumer</code> 模块的 <code>resources</code> 资源文件夹下建立 <code>application.yml</code> 文件，定义如下：</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">dubbo</span><span class="token punctuation">:</span>  <span class="token key atrule">application</span><span class="token punctuation">:</span>    <span class="token key atrule">name</span><span class="token punctuation">:</span> dubbo<span class="token punctuation">-</span>springboot<span class="token punctuation">-</span>demo<span class="token punctuation">-</span>consumer  <span class="token key atrule">protocol</span><span class="token punctuation">:</span>    <span class="token key atrule">name</span><span class="token punctuation">:</span> dubbo    <span class="token key atrule">port</span><span class="token punctuation">:</span> <span class="token number">-1</span>  <span class="token key atrule">registry</span><span class="token punctuation">:</span>    <span class="token key atrule">address</span><span class="token punctuation">:</span> zookeeper<span class="token punctuation">:</span>//localhost<span class="token punctuation">:</span><span class="token number">2181 </span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>在这个配置文件中，定义了 Dubbo 的应用名、Dubbo 协议信息、Dubbo 使用的注册中心地址。</p><h2 id="8-基于-Spring-配置服务端启动类"><a href="#8-基于-Spring-配置服务端启动类" class="headerlink" title="8. 基于 Spring 配置服务端启动类"></a>8. 基于 Spring 配置服务端启动类</h2><p>除了配置 Yaml 配置文件之外，我们还需要创建基于 Spring Boot 的启动类。</p><p>首先，我们先创建服务端的启动类。</p><p>在 <code>dubbo-spring-boot-demo-provider</code> 模块的 <code>org.apache.dubbo.springboot.demo.provider</code> 下建立 <code>Application</code> 类，定义如下：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">package</span> org<span class="token punctuation">.</span>apache<span class="token punctuation">.</span>dubbo<span class="token punctuation">.</span>springboot<span class="token punctuation">.</span>demo<span class="token punctuation">.</span>provider<span class="token punctuation">;</span><span class="token keyword">import</span> org<span class="token punctuation">.</span>apache<span class="token punctuation">.</span>dubbo<span class="token punctuation">.</span>config<span class="token punctuation">.</span>spring<span class="token punctuation">.</span>context<span class="token punctuation">.</span>annotation<span class="token punctuation">.</span>EnableDubbo<span class="token punctuation">;</span><span class="token keyword">import</span> org<span class="token punctuation">.</span>springframework<span class="token punctuation">.</span>boot<span class="token punctuation">.</span>SpringApplication<span class="token punctuation">;</span><span class="token keyword">import</span> org<span class="token punctuation">.</span>springframework<span class="token punctuation">.</span>boot<span class="token punctuation">.</span>autoconfigure<span class="token punctuation">.</span>SpringBootApplication<span class="token punctuation">;</span><span class="token annotation punctuation">@SpringBootApplication</span><span class="token annotation punctuation">@EnableDubbo</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ProviderApplication</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>        SpringApplication<span class="token punctuation">.</span><span class="token function">run</span><span class="token punctuation">(</span>ProviderApplication<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">,</span> args<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>在这个启动类中，配置了一个 <code>ProviderApplication</code> 去读取我们前面第 6 步中定义的 <code>application.yml</code> 配置文件并启动应用。</p><h2 id="9-基于-Spring-配置消费端启动类"><a href="#9-基于-Spring-配置消费端启动类" class="headerlink" title="9. 基于 Spring 配置消费端启动类"></a>9. 基于 Spring 配置消费端启动类</h2><p>同样的，我们需要创建消费端的启动类。</p><p>在 <code>dubbo-spring-boot-demo-consumer</code> 模块的 <code>org.apache.dubbo.springboot.demo.consumer</code> 下建立 <code>Application</code> 类，定义如下：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">package</span> org<span class="token punctuation">.</span>apache<span class="token punctuation">.</span>dubbo<span class="token punctuation">.</span>springboot<span class="token punctuation">.</span>demo<span class="token punctuation">.</span>consumer<span class="token punctuation">;</span><span class="token keyword">import</span> org<span class="token punctuation">.</span>apache<span class="token punctuation">.</span>dubbo<span class="token punctuation">.</span>config<span class="token punctuation">.</span>spring<span class="token punctuation">.</span>context<span class="token punctuation">.</span>annotation<span class="token punctuation">.</span>EnableDubbo<span class="token punctuation">;</span><span class="token keyword">import</span> org<span class="token punctuation">.</span>springframework<span class="token punctuation">.</span>boot<span class="token punctuation">.</span>SpringApplication<span class="token punctuation">;</span><span class="token keyword">import</span> org<span class="token punctuation">.</span>springframework<span class="token punctuation">.</span>boot<span class="token punctuation">.</span>autoconfigure<span class="token punctuation">.</span>SpringBootApplication<span class="token punctuation">;</span><span class="token annotation punctuation">@SpringBootApplication</span><span class="token annotation punctuation">@EnableDubbo</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ConsumerApplication</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>        SpringApplication<span class="token punctuation">.</span><span class="token function">run</span><span class="token punctuation">(</span>ConsumerApplication<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">,</span> args<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>在这个启动类中，配置了一个 <code>ConsumerApplication</code> 去读取我们前面第 7 步中定义的 <code>application.yml</code> 配置文件并启动应用。</p><h2 id="10-配置消费端请求任务"><a href="#10-配置消费端请求任务" class="headerlink" title="10. 配置消费端请求任务"></a>10. 配置消费端请求任务</h2><p>除了配置消费端的启动类，我们在 Spring Boot 模式下还可以基于<code>CommandLineRunner</code>去创建</p><p>在 <code>dubbo-spring-boot-demo-consumer</code> 模块的 <code>org.apache.dubbo.springboot.demo.consumer</code> 下建立 <code>Task</code> 类，定义如下：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">package</span> org<span class="token punctuation">.</span>apache<span class="token punctuation">.</span>dubbo<span class="token punctuation">.</span>springboot<span class="token punctuation">.</span>demo<span class="token punctuation">.</span>consumer<span class="token punctuation">;</span><span class="token keyword">import</span> java<span class="token punctuation">.</span>util<span class="token punctuation">.</span>Date<span class="token punctuation">;</span><span class="token keyword">import</span> org<span class="token punctuation">.</span>apache<span class="token punctuation">.</span>dubbo<span class="token punctuation">.</span>config<span class="token punctuation">.</span>annotation<span class="token punctuation">.</span>DubboReference<span class="token punctuation">;</span><span class="token keyword">import</span> org<span class="token punctuation">.</span>apache<span class="token punctuation">.</span>dubbo<span class="token punctuation">.</span>springboot<span class="token punctuation">.</span>demo<span class="token punctuation">.</span>DemoService<span class="token punctuation">;</span><span class="token keyword">import</span> org<span class="token punctuation">.</span>springframework<span class="token punctuation">.</span>boot<span class="token punctuation">.</span>CommandLineRunner<span class="token punctuation">;</span><span class="token keyword">import</span> org<span class="token punctuation">.</span>springframework<span class="token punctuation">.</span>stereotype<span class="token punctuation">.</span>Component<span class="token punctuation">;</span><span class="token annotation punctuation">@Component</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Task</span> <span class="token keyword">implements</span> <span class="token class-name">CommandLineRunner</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@DubboReference</span>    <span class="token keyword">private</span> DemoService demoService<span class="token punctuation">;</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">run</span><span class="token punctuation">(</span>String<span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span> args<span class="token punctuation">)</span> <span class="token keyword">throws</span> Exception <span class="token punctuation">{</span>        String result <span class="token operator">=</span> demoService<span class="token punctuation">.</span><span class="token function">sayHello</span><span class="token punctuation">(</span><span class="token string">"world"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"Receive result ======> "</span> <span class="token operator">+</span> result<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span>            <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token keyword">try</span> <span class="token punctuation">{</span>                    Thread<span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">1000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                    System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">" Receive result ======> "</span> <span class="token operator">+</span> demoService<span class="token punctuation">.</span><span class="token function">sayHello</span><span class="token punctuation">(</span><span class="token string">"world"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">InterruptedException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span>                    e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                    Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">interrupt</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token punctuation">}</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>在 <code>Task</code> 类中，通过<code>@DubboReference</code> 从 Dubbo 获取了一个 RPC 订阅，这个 <code>demoService</code> 可以像本地调用一样直接调用。在 <code>run</code>方法中创建了一个线程进行调用。</p><h2 id="11-启动应用"><a href="#11-启动应用" class="headerlink" title="11. 启动应用"></a>11. 启动应用</h2><p>截止第 10 步，代码就已经开发完成了，本小节将启动整个项目并进行验证。</p><p>首先是启动 <code>org.apache.dubbo.springboot.demo.provider.Application</code> ，等待一会出现如下图所示的日志（<code>Current Spring Boot Application is await</code>）即代表服务提供者启动完毕，标志着该服务提供者可以对外提供服务了。</p><pre class="line-numbers language-properties"><code class="language-properties"><span class="token attr-name">[Dubbo]</span> <span class="token attr-value">Current Spring Boot Application is await...</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>然后是启动<code>org.apache.dubbo.springboot.demo.consumer.Application</code> ，等待一会出现如下图所示的日志（<code>Hello world</code> ）即代表服务消费端启动完毕并调用到服务端成功获取结果。</p><pre class="line-numbers language-properties"><code class="language-properties"><span class="token attr-name">Receive</span> <span class="token attr-value">result ======> Hello world</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><h1 id="Dubbo-Admin"><a href="#Dubbo-Admin" class="headerlink" title="Dubbo Admin"></a>Dubbo Admin</h1><h2 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h2><p>参考：<a href="https://blog.csdn.net/qq_24950043/article/details/127914231">两种方式安装dubbo-admin、zookeeper</a>、<a href="https://codeantenna.com/a/l2tW73mLB5">M1 Mac下使用Docker安装zookeeper、dubbo-admin</a></p><h3 id="准备工作"><a href="#准备工作" class="headerlink" title="准备工作"></a>准备工作</h3><p>参考：<a href="https://juejin.cn/post/7103406988079398942">docker安装zookeeper</a>、<a href="https://blog.csdn.net/duyun0/article/details/128437451">Docker安装Zookeeper教程（超详细）</a></p><ul><li>安装 zookeeper</li></ul><pre class="line-numbers language-powershell"><code class="language-powershell"> docker search zookeeper     docker pull zookeeper  docker images              <span class="token comment" spellcheck="true"># 查看下载的本地镜像</span> docker inspect zookeeper   <span class="token comment" spellcheck="true"># 查看zookeeper详细信息</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><ul><li>创建ZooKeeper 挂载目录（数据挂载目录、配置挂载目录和日志挂载目录）</li></ul><pre class="line-numbers language-powershell"><code class="language-powershell">mkdir <span class="token operator">-</span>p <span class="token operator">/</span>mydata<span class="token operator">/</span>zookeeper<span class="token operator">/</span><span class="token keyword">data</span> <span class="token comment" spellcheck="true"># 数据挂载目录</span>mkdir <span class="token operator">-</span>p <span class="token operator">/</span>mydata<span class="token operator">/</span>zookeeper<span class="token operator">/</span>conf <span class="token comment" spellcheck="true"># 配置挂载目录</span>mkdir <span class="token operator">-</span>p <span class="token operator">/</span>mydata<span class="token operator">/</span>zookeeper<span class="token operator">/</span>logs <span class="token comment" spellcheck="true"># 日志挂载目录</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><ul><li>启动ZooKeeper容器</li></ul><pre class="line-numbers language-powershell"><code class="language-powershell">docker run <span class="token operator">-</span>d <span class="token operator">--</span>name zookeeper <span class="token operator">--</span>privileged=true <span class="token operator">-</span>p 2181:2181  <span class="token operator">-</span>v <span class="token operator">/</span>mydata<span class="token operator">/</span>zookeeper<span class="token operator">/</span><span class="token keyword">data</span>:<span class="token operator">/</span><span class="token keyword">data</span> <span class="token operator">-</span>v <span class="token operator">/</span>mydata<span class="token operator">/</span>zookeeper<span class="token operator">/</span>conf:<span class="token operator">/</span>conf <span class="token operator">-</span>v <span class="token operator">/</span>mydata<span class="token operator">/</span>zookeeper<span class="token operator">/</span>logs:<span class="token operator">/</span>datalog zookeeper<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>参数说明</p><pre class="line-numbers language-powershell"><code class="language-powershell"><span class="token operator">-</span>d  <span class="token comment" spellcheck="true"># 表示在一直在后台运行容器</span><span class="token operator">--</span>name <span class="token comment" spellcheck="true"># 设置创建的容器名称</span><span class="token operator">-</span>p 2181:2181 <span class="token comment" spellcheck="true"># 对端口进行映射，将本地2181端口映射到容器内部的2181端口</span><span class="token operator">-</span>v <span class="token comment" spellcheck="true"># 将本地目录(文件)挂载到容器指定目录；</span><span class="token operator">--</span>restart always <span class="token comment" spellcheck="true">#始终重新启动zookeeper，看需求设置不设置自启动</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>查看容器</li></ul><pre class="line-numbers language-powershell"><code class="language-powershell">docker <span class="token function">ps</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><ul><li>添加ZooKeeper配置文件，在挂载配置文件目录(&#x2F;mydata&#x2F;zookeeper&#x2F;conf)下，新增zoo.cfg 配置文件，配置内容如下：</li></ul><pre class="line-numbers language-properties"><code class="language-properties"><span class="token attr-name">dataDir</span><span class="token punctuation">=</span><span class="token attr-value">/data   # 保存zookeeper中的数据</span><span class="token attr-name">clientPort</span><span class="token punctuation">=</span><span class="token attr-value">2181 # 客户端连接端口，通常不做修改</span><span class="token attr-name">dataLogDir</span><span class="token punctuation">=</span><span class="token attr-value">/datalog</span><span class="token attr-name">tickTime</span><span class="token punctuation">=</span><span class="token attr-value">2000   # 通信心跳时间</span><span class="token attr-name">initLimit</span><span class="token punctuation">=</span><span class="token attr-value">5     # LF(leader - follower)初始通信时限</span><span class="token attr-name">syncLimit</span><span class="token punctuation">=</span><span class="token attr-value">2     # LF 同步通信时限</span><span class="token attr-name">autopurge.snapRetainCount</span><span class="token punctuation">=</span><span class="token attr-value">3</span><span class="token attr-name">autopurge.purgeInterval</span><span class="token punctuation">=</span><span class="token attr-value">0</span><span class="token attr-name">maxClientCnxns</span><span class="token punctuation">=</span><span class="token attr-value">60</span><span class="token attr-name">standaloneEnabled</span><span class="token punctuation">=</span><span class="token attr-value">true</span><span class="token attr-name">admin.enableServer</span><span class="token punctuation">=</span><span class="token attr-value">true</span><span class="token attr-name">server.1</span><span class="token punctuation">=</span><span class="token attr-value">localhost:2888:3888;2181</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>启动zk客户端</li></ul><pre class="line-numbers language-powershell"><code class="language-powershell"><span class="token comment" spellcheck="true"># 进入zookeeper 容器内部</span>docker exec <span class="token operator">-</span>it zookeeper <span class="token operator">/</span>bin<span class="token operator">/</span>bash<span class="token comment" spellcheck="true"># 检查容器状态</span>docker exec <span class="token operator">-</span>it zookeeper <span class="token operator">/</span>bin<span class="token operator">/</span>bash <span class="token punctuation">.</span><span class="token operator">/</span>bin<span class="token operator">/</span>zkServer<span class="token punctuation">.</span>sh status<span class="token comment" spellcheck="true"># 进入控制台</span>docker exec <span class="token operator">-</span>it zookeeper zkCli<span class="token punctuation">.</span>sh<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="docker-安装"><a href="#docker-安装" class="headerlink" title="docker 安装"></a>docker 安装</h3><ul><li>拉取镜像</li></ul><pre class="line-numbers language-powershell"><code class="language-powershell">docker search dubbo<span class="token operator">-</span>admindocker pull apache<span class="token operator">/</span>dubbo<span class="token operator">-</span>admin<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><ul><li>创建容器</li></ul><pre class="line-numbers language-bash"><code class="language-bash">docker run -d --name dubbo-admin -p 8081:8080 -e admin.registry.address<span class="token operator">=</span>zookeeper://ip地址:2181 -e admin.config-center<span class="token operator">=</span>zookeeper://ip地址:2181 -e admin.metadata-report.address<span class="token operator">=</span>zookeeper://ip地址:2181 --restart<span class="token operator">=</span>always apache/dubbo-admin<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><ul><li>访问dubbo-admin</li></ul><p>访问地址： <code>http://ip地址:8081</code>，用户名&#x2F;密码：root&#x2F;root</p><h3 id="手动安装"><a href="#手动安装" class="headerlink" title="手动安装"></a>手动安装</h3><p><a href="https://blog.csdn.net/qq_24950043/article/details/127914231">两种方式安装dubbo-admin、zookeeper</a></p><h1 id="Dubbo-高级特性"><a href="#Dubbo-高级特性" class="headerlink" title="Dubbo 高级特性"></a><strong>Dubbo</strong> 高级特性</h1><h2 id="序列化"><a href="#序列化" class="headerlink" title="序列化"></a>序列化</h2><p>dubbo 内部已经将序列化和反序列化的过程内部封装了</p><p>我们只需要在定义pojo类时实现Serializable接口即可</p><p>一般会定义一个公共的pojo模块，让生产者和消费者都依赖该模块。</p><h2 id="地址缓存"><a href="#地址缓存" class="headerlink" title="地址缓存"></a>地址缓存</h2><p><strong>注册中心挂了，服务是否可以正常访问？</strong></p><p>可以，因为dubbo服务消费者在第一次调用时，会将服务提供方地址缓存到消费者本地，以后再调用则不会访问注册中心。</p><p>当服务提供者地址发生变化时，注册中心会通知服务消费者。</p><h2 id="超时与重试"><a href="#超时与重试" class="headerlink" title="超时与重试"></a>超时与重试</h2><p>服务消费者在调用服务提供者的时候发生了阻塞、等待的情形，这个时候，服务消费者会一直等待下去。在某个峰值时刻，大量的请求都在同时请求服务消费者，会造成线程的大量堆积，势必会造成雪崩。</p><ul><li><p>dubbo 利用超时机制来解决这个问题，设置一个超时时间，在这个时间段内，无法完成服务访问，则自动断开连接。</p></li><li><p>使用timeout属性配置超时时间，默认值1000，单位毫秒。</p></li></ul><p>设置了超时时间，在这个时间段内，无法完成服务访问，则自动断开连接。如果出现网络抖动，则这一次请求就会失败。</p><ul><li><p>Dubbo 提供重试机制来避免类似问题的发生。</p></li><li><p>通过 retries 属性来设置重试次数。默认为 2 次。</p></li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@DubboService</span><span class="token punctuation">(</span>timeout <span class="token operator">=</span> <span class="token number">1000</span><span class="token punctuation">,</span> retries <span class="token operator">=</span> <span class="token number">2</span><span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><h2 id="多版本"><a href="#多版本" class="headerlink" title="多版本"></a>多版本</h2><p>灰度发布：当出现新功能时，会让一部分用户先使用新功能，用户反馈没问题时，再将所有用户迁移到新功能。</p><ul><li>dubbo 中使用 version 属性来设置和调用同一个接口的不同版本</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@DubboService</span><span class="token punctuation">(</span>version <span class="token operator">=</span> <span class="token string">"v1.0"</span><span class="token punctuation">)</span>   <span class="token comment" spellcheck="true">// 提供方</span><span class="token annotation punctuation">@DubboReference</span><span class="token punctuation">(</span>version <span class="token operator">=</span> <span class="token string">"v1.0"</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// 使用方</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><h2 id="负载均衡"><a href="#负载均衡" class="headerlink" title="负载均衡"></a>负载均衡</h2><p>Dubbo 提供 4 种负载均衡策略：</p><ul><li><p>Random ：按权重随机，默认值。按权重设置随机概率。</p></li><li><p>RoundRobin ：按权重轮询。</p></li><li><p>LeastActive：最少活跃调用数，相同活跃数的随机。</p></li><li><p>ConsistentHash：一致性 Hash，相同参数的请求总是发到同一提供者。</p></li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@DubboService</span><span class="token punctuation">(</span>weight <span class="token operator">=</span> <span class="token number">100</span><span class="token punctuation">)</span><span class="token annotation punctuation">@DubboService</span><span class="token punctuation">(</span>weight <span class="token operator">=</span> <span class="token number">200</span><span class="token punctuation">)</span><span class="token annotation punctuation">@DubboReference</span><span class="token punctuation">(</span>loadbalance <span class="token operator">=</span> <span class="token string">"random"</span><span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><h2 id="集群容错"><a href="#集群容错" class="headerlink" title="集群容错"></a>集群容错</h2><p>Dubbo 提供的集群容错模式：</p><ul><li><p>Failover Cluster：失败重试。默认值。当出现失败，重试其它服务器 ，默认重试2次，使用 retries 配置。一般用于读操作</p></li><li><p>Failfast Cluster ：快速失败，只发起一次调用，失败立即报错。通常用于写操作。</p></li><li><p>Failsafe Cluster ：失败安全，出现异常时，直接忽略。返回一个空结果。</p></li><li><p>Failback Cluster ：失败自动恢复，后台记录失败请求，定时重发。通常用于消息通知操作。</p></li><li><p>Forking Cluster ：并行调用多个服务器，只要一个成功即返回。</p></li><li><p>Broadcast Cluster ：广播调用所有提供者，逐个调用，任意一台报错则报错。</p></li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@DubboReference</span><span class="token punctuation">(</span>cluster <span class="token operator">=</span> <span class="token string">"failover"</span><span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><h2 id="服务降级"><a href="#服务降级" class="headerlink" title="服务降级"></a>服务降级</h2><p>服务降级方式</p><ul><li>mock&#x3D;force:return null   表示消费方对该服务的方法调用都直接返回 null 值，不发起远程调用。用来屏蔽不重要服务不可用时对调用方的影响。</li><li>mock&#x3D;fail:return null    表示消费方对该服务的方法调用在失败后，再返回null 值，不抛异常。用来容忍不重要服务不稳定时对调用方的影响。</li></ul><pre><code>@DubboReference(mock = &quot;force:return null&quot;)</code></pre><h1 id="📚参考资料"><a href="#📚参考资料" class="headerlink" title="📚参考资料"></a>📚参考资料</h1><ul><li><a href="https://www.bilibili.com/video/BV1VE411q7dX">dubbo快速入门</a></li><li><a href="https://cn.dubbo.apache.org/zh-cn/overview/quickstart/java/brief/">零基础快速部署一个微服务应用 | Apache Dubbo</a></li></ul><h1 id="❤️Sponsor"><a href="#❤️Sponsor" class="headerlink" title="❤️Sponsor"></a>❤️Sponsor</h1><p>您的支持是我不断前进的动力，如果您感觉本文对您有所帮助的话，可以考虑打赏一下本文，用以维持本博客的运营费用，拒绝白嫖，从你我做起！🥰🥰🥰</p><table>  <tbody>     <tr>         <td style="text-align:center;">支付宝</td>         <td style="text-align:center;">微信</td>     </tr>   <tr>    <td style="text-align:center;" ><img width="200" src="https://jwt1399.top/medias/reward/alipay.png"></td>          <td style="text-align:center;"><img width="200" src="https://jwt1399.top/medias/reward/sponor_wechat.png"></td>       </tr></tbody></table>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;h1 id=&quot;架构演变&quot;&gt;&lt;a href=&quot;#架构演变&quot; class=&quot;headerlink&quot; title=&quot;架构演变&quot;&gt;&lt;/a&gt;架构演变&lt;/h1&gt;&lt;img src=&quot;https://img.jwt1399.top/img/202307201755344.png&quot;</summary>
        
      
    
    
    
    <category term="Spring" scheme="https://jwt1399.top/categories/Spring/"/>
    
    
    <category term="Dubbo" scheme="https://jwt1399.top/tags/Dubbo/"/>
    
  </entry>
  
  <entry>
    <title>可搜索加密：B-SSE方案</title>
    <link href="https://jwt1399.top/posts/63937.html"/>
    <id>https://jwt1399.top/posts/63937.html</id>
    <published>2022-12-12T13:20:01.000Z</published>
    <updated>2024-08-03T02:07:04.184Z</updated>
    
    <content type="html"><![CDATA[<center><b>论文名称：《Encrypted Keyword Search Mechanism Based on Bitmap Index for Personal Storage Services》</b></center><ul><li>地址：<a href="https://ieeexplore.ieee.org/document/7011244">https://ieeexplore.ieee.org/document/7011244</a></li><li>作者：Yong Ho Hwang; Jae Woo Seo; ll Joo Kim</li><li>单位：Samsung Research</li><li>会议：<a href="https://ieeexplore.ieee.org/document/7011244">2014 IEEE 13th International Conference on Trust, Security and Privacy in Computing and Communications</a>(TrustCom)</li><li>时间：2014年9月</li></ul><center><font size=5><b>B-SSE</b></center><table><thead><tr><th align="center">单关键字</th><th align="center"><strong>动态更新</strong></th><th align="center">可验证</th></tr></thead><tbody><tr><td align="center">✅</td><td align="center">✅</td><td align="center">❌</td></tr></tbody></table><h1 id="1、方案简介"><a href="#1、方案简介" class="headerlink" title="1、方案简介"></a>1、方案简介</h1><ul><li><p>提出了一种基于 <code>BitMap</code> 索引的关键字动态可搜索机制，该机制以<code>倒排索引</code>形式表示一组文件。</p></li><li><p>与以前的工作相比，所提出的机制具有<code>快速的搜索时间</code>和<code>较小的索引大小</code>。</p></li><li><p>对随机预言机模型中的<code>自适应选择关键字攻击（CKA2）</code>是安全的。</p></li><li><p>在 <code>Couchbase</code> 上实现了 <code>B-SSE</code>，1w个关键字，1w个文件，搜索时间大约350ms</p></li></ul><h1 id="2、具体实现"><a href="#2、具体实现" class="headerlink" title="2、具体实现"></a>2、具体实现</h1><table><thead><tr><th>符号</th><th>描述</th></tr></thead><tbody><tr><td>F</td><td>总文件集合</td></tr><tr><td>F(w)</td><td>包含关键字w的文件集合</td></tr><tr><td>W</td><td>表示唯一关键字的集合</td></tr><tr><td>W(f)</td><td>文件 f 中包含的唯一关键字的集合</td></tr></tbody></table><p>我们构造了一个由索引服务器和文件服务器组成的应用服务器。</p><ul><li>索引服务器维护搜索表$T_s$</li></ul><p>当用户请求搜索查询时，它将关联的文件标识符返回到文件服务器，并且当用户请求添加和删除时，它会更新与搜索表$T_s$中添加&#x2F;删除的文件关联的位图.</p><ul><li>文件服务器维护文件表$T_f$</li></ul><p>它返回与索引服务器给出的文件标识符相关联的加密文件，并根据用户的请求添加或删除文件表$T_f$中的文件。</p><p>索引服务器和文件服务器通过其SDK（软件开发工具包）相互交互，该SDK建立连接并支持读&#x2F;写和其他功能</p><table><thead><tr><th>Gen</th><th>$T_s$</th><th>$T_f$</th></tr></thead><tbody><tr><td><img src="https://img.jwt1399.top/img/202301061857310.png"></td><td><img src="https://img.jwt1399.top/img/202301061857900.png"></td><td><img src="https://img.jwt1399.top/img/202301061857614.png"></td></tr><tr><td>Search</td><td>Add</td><td>Add</td></tr><tr><td><img src="https://img.jwt1399.top/img/202301061857425.png"></td><td><img src="https://img.jwt1399.top/img/202301061857028.png"></td><td><img src="https://img.jwt1399.top/img/202301061857950.png"></td></tr></tbody></table><h2 id="Gen-1-k"><a href="#Gen-1-k" class="headerlink" title="$Gen(1^k)$"></a>$Gen(1^k)$</h2><ul><li><p>generate random keys $K_1, K_2 ← {0, 1}^k$,</p></li><li><p>generate a encryption key $K_e ← SKE.Gen(1^k)$ </p><ul><li>Let $SKE &#x3D; (Gen, Enc, Dec)$ be a <strong>symmetric encryption scheme</strong></li></ul></li><li><p>output $K &#x3D; (K_1, K_2, K_e)$</p></li></ul><h2 id="BuildIndex-F-K"><a href="#BuildIndex-F-K" class="headerlink" title="$BuildIndex(F, K)$"></a>$BuildIndex(F, K)$</h2><ul><li><p>Building the search table $T_s$：</p><p>For all $i ∈ [w]$</p></li></ul><ol><li><p>compute $id(w_i) &#x3D; P(K_1, w_i)$</p><ul><li><p>$P:{0,1}^k × {0,1}^ω → {0,1}^{l_w}$ be a pseudo-random function </p></li><li><p>where $ω &#x3D; |w_i|$ and $l_w &#x3D; |id(w_i)|$</p></li></ul></li><li><p>generate $id(F(w_i))$</p></li><li><p>make the bitmap $B_m(w_i)$ where $B_m(w_i)[j]$ &#x3D; 1 for all $j ∈ id(F(w_i))$</p></li><li><p>choose random $r_i ← {0,1}^{l_r}$ and compute $x_i &#x3D; h_i ⊕ B_m(w_i)$ where $h_i &#x3D; H(K_{w_i}||r_i)$ and $K_{w_i} &#x3D;P(K_2, w_i)$</p><ul><li>$H : {0,1}^{k+l_r} → {0,1}^m$ be a hash function where $l_r$ is the length of random numbers</li></ul></li><li><p>store $(key, value) &#x3D; (id(w_i), x_i||r_i)$</p></li></ol><ul><li>Building the file table $T_f$ :</li></ul><p>​For all $j ∈ id(f)$, where $f ≤ m$</p><ol><li>compute $c_j ← SKE.Enc_{K_e} (f_j)$</li></ol><ol start="2"><li>store $(key, value) &#x3D; (j, c_j)$</li></ol><ul><li>output the index $I &#x3D; (T_s, T_f)$.</li></ul><h2 id="SearchToken-K-w-i"><a href="#SearchToken-K-w-i" class="headerlink" title="$SearchToken(K, w_i)$"></a>$SearchToken(K, w_i)$</h2><ul><li>compute $id(w_i) &#x3D; P(K_1, w_i)$ and $K_{w_i} &#x3D; P(K_2, w_i)$ </li><li>output the search token $t_s &#x3D; (id(w_i), K_{w_i})$</li></ul><h2 id="Search-I-t-s"><a href="#Search-I-t-s" class="headerlink" title="$Search(I, t_s)$"></a>$Search(I, t_s)$</h2><ul><li><p>compute $T_s[id(w_i)] &#x3D; x_i||r_i$</p></li><li><p>recover the bitmap $B_m(w_i) &#x3D; x_i ⊕ H(K_{w_i} ||r_i)$ </p></li><li><p>for all $j$ such that $B_m(w_i)[j] &#x3D; 1$, return the identifiers $j$ and the encrypted files $c_j$ </p></li><li><p>$f &#x3D; SKE.Dec_{K_e} (c)$</p></li></ul><h2 id="AddToken-K-f"><a href="#AddToken-K-f" class="headerlink" title="$AddToken(K,f)$"></a>$AddToken(K,f)$</h2><p>For an added file $f$ and its keywords $w_i ∈ W(f)$</p><ul><li><p>compute $id(w_i) &#x3D; P(K_1, w_i)$</p></li><li><p>choose random $r_i ← {0,1}^r$ and compute $h_i &#x3D; H(K_{w_i} ||r_i)$ where $K_{w_i} &#x3D; P(K_2, w_i)$</p></li><li><p>output  $t_a &#x3D; ({id(w_i)}, {h_i}, {r_i})$ and  $c ← SKE.Enc_{K_e} (f)$</p></li></ul><h2 id="DeleteToken-K-f-j"><a href="#DeleteToken-K-f-j" class="headerlink" title="$DeleteToken(K, f_j)$"></a>$DeleteToken(K, f_j)$</h2><p>For a deleted file $f_j$ and its keywords $w_i ∈ W(f_j)$</p><ul><li>compute $id(w_i) &#x3D; P(K_1, w_i)$</li><li>output  $t_d &#x3D; (id(f_j ), {id(w_i)})$</li></ul><h2 id="Add-I-t-a-c"><a href="#Add-I-t-a-c" class="headerlink" title="$Add(I, t_a, c)$"></a>$Add(I, t_a, c)$</h2><ul><li><p>Adding the encrypted file in $T_f$ :</p><ul><li><p>Scan  $T_f$ </p></li><li><p>if  $T_f[j] &#x3D; null$, assign the subscript $j$ to the added file and store $(key, value) &#x3D; (j, c_j )$ in the $j-th$ row; </p></li><li><p>if  $T_f[j]\neq null$, return ⊥ indicating an error.</p></li></ul></li><li><p>Updating the bitmaps for $W(f)$ in $T_s$:</p><p>For given $id(w_i)$ appeared for the first time</p><ul><li>store $(key, value) &#x3D; (id(w_i), x_i||r_i)$ where $x_i &#x3D; h_i ⊕ B_m({j})$</li></ul><p>For given $id(w_i)$ existed before</p><ul><li>find the value $T_s[id(w_i)] &#x3D; x_i||r_i$</li><li>compute $new ~~ x_i &#x3D; old ~~ x_i ⊕ B_m({j})$ </li><li>where $B_m({j})$ is the m-bit array such that $j-th$ bit is 1 and the other bits are 0.</li></ul></li></ul><h2 id="Delete-I-t-d"><a href="#Delete-I-t-d" class="headerlink" title="$Delete(I, t_d)$"></a>$Delete(I, t_d)$</h2><ul><li>Deleting the encrypted file in $T_f$ :<ul><li>Find the row with $key &#x3D; j$  and delete it in $T_f$ .</li></ul></li><li>Updating the bitmaps for $W(f_j)$ in $T_s$：<ul><li>For all given $id(w_i)$</li><li>find the value $T_s[id(w_i)] &#x3D; x_i||r_i$,</li><li>compute $x_i &#x3D; x_i ⊕ B_m({j})$</li></ul></li></ul><h2 id="BITMAP-EXPANSION"><a href="#BITMAP-EXPANSION" class="headerlink" title="BITMAP EXPANSION"></a>BITMAP EXPANSION</h2><p>为了存储超过 m 个文件，位图应扩展到 m 位以上。但是，简单地将额外的位添加到 m 位数组会导致系统参数长度的变化，例如哈希值和随机数。这是我们结构中不可取的修改。</p><p>我们使用链接列表数据结构（LinkedList）来扩展位图大小。在存储 (m + 1) 个文件时，服务器为第 (m + 1) 个文件中包含的关键字生成额外的 m 位数组，其比特位置与 {m + 1,…, 2m}，并以链接列表形式绑定两个位图。链接列表形式包括表示下一个链接的地址（即位图）。为了隐藏第（m+1）个文件与其关键字标识符之间的关系，我们以与屏蔽位图相同的方式屏蔽地址。</p><p>the search table Ts stores<br>$$<br>(\text { key, value })&#x3D;\left(i d\left(w_i\right), x_{i, j}\left|r_{x_{i, j}}\right| y_{i, j} | r_{y_{i, j}}\right)<br>$$<br>where, for $1 ≤ i ≤ w$, $j ≥ 1$ and $K_3 ← {0, 1}^k$<br>$$<br>x_{i, j}&#x3D;\mathrm{H}\left(K_{w_i} | r_{x_{i, j}}\right) \oplus \mathrm{B}<em>{\mathrm{m}(j)}\left(w_i\right), \quad K</em>{w_i}&#x3D;\mathrm{P}\left(K_2, w_i\right) \<br>y_{i, j}&#x3D;\mathrm{H}\left(K_{a_i} | r_{y_{i, j}}\right) \oplus addr_{i, j},\quad K_{a_i}&#x3D;\mathrm{P}\left(K_3, w_i\right)<br>$$<br>The value $addr_{i,j}$  indicates the location of the $(j + 1)-th$ bitmap for the keyword $w_i$</p><p>each bitmap $B_m(j)$ is the set representation of file identifiers ${(j − 1) · m + 1, . . . , j · m}$.</p><p>When adding a new bitmap $B_{m(j+1)}$ for the keyword $w_i$, a random value is assigned to $addr_{i,j}$ by the exclusive-OR operation and becomes key to find the bitmap $B_{m(j+1)}$，where the $addr_{i,j+1}$ is set as zero bits. To search the files associated with the keyword $w_i$, we can consider the following SearchToken<br>$$<br>t_s&#x3D;\left{\operatorname{id}\left(w_i\right), K_{w_i}, K_{a_i}\right}<br>$$<br>The server first finds the bitmap $B_m(1)(w_i)$ from the keyword identifiers $id(w_i)$ and recovers $addr_{i,j}$ by using the key $K_{a_i}$ recursively. If $addr_{i,j}$ is zero bits, the server stops searching the bitmaps for the keyword $w_i$. </p>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;center&gt;&lt;b&gt;论文名称：《Encrypted Keyword Search Mechanism Based on Bitmap Index for Personal Storage Services》&lt;/b&gt;&lt;/center&gt;

&lt;ul&gt;
&lt;li&gt;地址：&lt;a</summary>
        
      
    
    
    
    <category term="Data" scheme="https://jwt1399.top/categories/Data/"/>
    
    
    <category term="可搜索加密" scheme="https://jwt1399.top/tags/%E5%8F%AF%E6%90%9C%E7%B4%A2%E5%8A%A0%E5%AF%86/"/>
    
  </entry>
  
  <entry>
    <title>可搜索加密：DVSSE方案</title>
    <link href="https://jwt1399.top/posts/49068.html"/>
    <id>https://jwt1399.top/posts/49068.html</id>
    <published>2022-12-12T06:04:33.000Z</published>
    <updated>2024-08-03T02:07:17.501Z</updated>
    
    <content type="html"><![CDATA[<center><b>论文名称：《Dynamic Verifiable Encrypted Keyword Search Using Bitmap Index and Homomorphic MAC》</b></center><ul><li>作者：Rajkumar Ramasamy; S. Sree Vivek; Praveen George; Bharat S. Rawal Kshatriya</li><li>单位：Samsung R&amp;D Institute India</li><li>会议： <a href="https://ieeexplore.ieee.org/document/7987223">2017 IEEE 4th International Conference on Cyber Security and Cloud Computing (CSCloud)</a></li><li>时间：2017年7月</li></ul><table><thead><tr><th align="center">多关键字</th><th align="center">模糊搜索</th><th align="center">可验证</th></tr></thead><tbody><tr><td align="center">❌</td><td align="center">❌</td><td align="center">✅</td></tr><tr><td align="center"><strong>动态更新</strong></td><td align="center"><strong>安全性</strong></td><td align="center"><strong>复杂度</strong></td></tr><tr><td align="center">✅</td><td align="center">CKA2 + CMA</td><td align="center">$O(M)$</td></tr></tbody></table><p>注：<em>M: Total number of Keywords</em></p><h1 id="0、基础概念"><a href="#0、基础概念" class="headerlink" title="0、基础概念"></a>0、基础概念</h1><p><code>Honest But Curious Cloud Service Provider (HBC-CSP)</code>：诚实但好奇的云服务提供商</p><ul><li>安全地存储加密数据并诚实执行搜索操作。它试图从系统中学习存储数据的任何有用信息，但不会偏离协议。</li></ul><p><code>Semi Honest But Curious Cloud Service Provider（SHBC-CSP）</code>：半诚实但好奇的云服务提供商</p><ul><li>诚实地存储加密数据，它试图从系统中学习存储数据的任何有用信息，并尝试以隐蔽的方式偏离协议。</li><li>我们认为这个强大的SHBC-CSP对手可能会自私地操纵搜索结果以节省其计算或下载带宽</li></ul><h1 id="1、方案简介"><a href="#1、方案简介" class="headerlink" title="1、方案简介"></a>1、方案简介</h1><p>本文考虑了在 SHBC-CSP 存在的情况下的 SSE。在 SHBC-CSP 面前为 SSE <strong>定义了</strong>一个新的安全概念，<strong>设计了</strong>两个新的 SSE 方案，并在提议的安全概念中正式<strong>证明了</strong>它们的安全性。动态可验证加密关键字搜索（DVSSE）是据我们所知的<strong>第一个</strong>既动态又可验证的 SSE 方案。</p><p>本文贡献：</p><ul><li><p>分析了 SHBC-CSP 存在下加密关键字搜索的安全性，并提供了一种新的安全模型来处理这种现实对手。</p></li><li><p>[6]中提出的方案缺乏验证检索结果正确性的规定。因此，Hwang等人的IND-CKA2安全方案在SHBC-CSP存在的情况下是不安全的。</p></li><li><p>我们提出了两种新方案，在SHBC-CSP存在的情况下进行加密关键字搜索，并在新提出的安全模型中证明其安全性。</p></li><li><p>我们的第一个方案是对[6]中提出的现有基于位图的加密关键字搜索的改进。尽管此方案提供了搜索结果可验证性，但它无法动态更新。</p></li><li><p>我们的第二种方案提供了位图的机密性，支持动态更新和搜索结果可验证性，这是避免访问模式泄漏所必需的。为了实现动态更新和搜索结果的可验证性，我们利用复合残差设计了一种新的同态MAC。</p></li></ul><h1 id="2、安全性"><a href="#2、安全性" class="headerlink" title="2、安全性"></a>2、安全性</h1><p>DynamicIND-CKA2-security</p><p>Setup ：C 生成 DVSSE 方案的密钥K，设置系统参数Params。A选择一个文档集合F，生成F中存在的关键字W的列表，并选择一个关键字W *∈W并将其交给c。c生成F的安全索引I并将其返回给A.C选择位b R←{0,1}。如果b&#x3D;0,C设置实际加密位图x *(对应于w *)。如果b&#x3D;1,C在加密位图的范围内随机选择x *。</p><p>Training Phase:</p><p>A 被允许查询以下预言机，其限制是 A 不能对 w * 进行任何预言机查询</p><p>OSearchT oken(w): Returns search token τs.</p><p>• OAddT oken(f ): Returns add token τa.</p><p>• ODeleteT oken(f ): Returns delete token τd.</p><p>• OV erif y(T ag,x,id(w)): Returns Accept or Reject.</p><p>• OBitmapDecryption(x): Returns Bm(w).</p><p> Challenge Phase:</p><p>C 将 id(w∗) 提供给 A</p><p>Guess:</p><p>A 输出一个位b ‘，如果x *对w *对应的位图进行加密，则b ‘为0，否则输出1。</p><p>如果对于所有概率多项式时间对手 A，该方案是 IND-CKA2 安全的，则存在一个可忽略的函数 negl(.) 使得：</p><p>P r[DV SSEIND-CKA2A (κ) → (b &#x3D; b′)] ≤ 1 2 + negl(κ)</p><h1 id="3、具体实现"><a href="#3、具体实现" class="headerlink" title="3、具体实现"></a>3、具体实现</h1><h2 id="方案一：VSSE"><a href="#方案一：VSSE" class="headerlink" title="方案一：VSSE"></a>方案一：VSSE</h2><blockquote><p>consists five algorithms $(KeyGen, BuildIndex, SearchToken, Search, Verify)$</p></blockquote><h3 id="KeyGen-κ"><a href="#KeyGen-κ" class="headerlink" title="KeyGen(κ)"></a>KeyGen(κ)</h3><p>Choose three cryptographic MAC’s defined as follows:</p><ul><li><p>$H_1 : {0, 1}^κ × {0, 1}^∗ → {0, 1}^κ$</p></li><li><p>$H_2 : {0, 1}^κ × {0, 1}^∗× {0, 1}^κ → {0, 1}^κ$</p></li><li><p>$H_3 : {0, 1}^κ × {0, 1}^n × {0, 1}^κ → {0, 1}^κ$</p></li></ul><p>Where, the first inputs are cryptographic keys.</p><p>Output the key $K &#x3D; &lt;K_1, K_2, K_e, K_h〉 \stackrel{R}← {0,1}^κ$</p><h3 id="BuildIndex-F-K"><a href="#BuildIndex-F-K" class="headerlink" title="BuildIndex(F, K)"></a>BuildIndex(F, K)</h3><p>$P:{0,1}^k × {0,1}^ω → {0,1}^{l_w}$ be a pseudo-random function where $ω &#x3D; |w_i|$ and $l_w &#x3D; |id(w_i)|$ for wi ∈ W;</p><p> the keyword identifier id(wi) is computed by a pseudo-random function P.</p><ul><li><p>Building the search table $T_s$：</p><p>For all $i ∈ [w]$,</p></li></ul><ol><li>compute $id(w_i) &#x3D; P(K_1, w_i)$</li><li>generate $id(F(w_i))$</li><li>make the bitmap $B_m(w_i)$ where $B_m(w_i)[j]$ &#x3D; 1 for all $j ∈ id(F(w_i))$</li><li>choose random $r_i ← {0,1}^r$ and compute $x_i &#x3D; h_i ⊕ B_m(w_i)$ where $h_i &#x3D; H(K_{w_i}||r_i)$ and $K_{w_i} &#x3D;P(K_2, w_i)$</li><li>store $(key, value) &#x3D; (id(w_i), x_i||r_i)$</li></ol><ul><li>Building the file table $T_f$ :</li></ul><p>​For all $j ∈ id(F)$, where $f ≤ m$</p><ol><li>compute $c_j ← SKE.Enc_{K_e} (f_j)$</li></ol><ol start="2"><li>store $(key, value) &#x3D; (j, c_j)$</li></ol><ul><li>output the index $I &#x3D; (T_s, T_f)$.</li></ul><h2 id="方案二：DVSSE"><a href="#方案二：DVSSE" class="headerlink" title="方案二：DVSSE"></a>方案二：DVSSE</h2>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;center&gt;&lt;b&gt;论文名称：《Dynamic Verifiable Encrypted Keyword Search Using Bitmap Index and Homomorphic MAC》&lt;/b&gt;&lt;/center&gt;

&lt;ul&gt;
&lt;li&gt;作者：Rajkumar</summary>
        
      
    
    
    
    <category term="Data" scheme="https://jwt1399.top/categories/Data/"/>
    
    
    <category term="可搜索加密" scheme="https://jwt1399.top/tags/%E5%8F%AF%E6%90%9C%E7%B4%A2%E5%8A%A0%E5%AF%86/"/>
    
  </entry>
  
  <entry>
    <title>Java-JUC</title>
    <link href="https://jwt1399.top/posts/3092.html"/>
    <id>https://jwt1399.top/posts/3092.html</id>
    <published>2022-12-06T12:38:38.000Z</published>
    <updated>2023-05-25T09:58:38.748Z</updated>
    
    <content type="html"><![CDATA[<h1 id="⓪基础"><a href="#⓪基础" class="headerlink" title="⓪基础"></a>⓪基础</h1><h2 id="❶进程-amp-线程"><a href="#❶进程-amp-线程" class="headerlink" title="❶进程&amp;线程"></a>❶进程&amp;线程</h2><ul><li>进程：指一个内存中运行的应用程序，每个进程都有自己独立的一块内存空间。</li><li>线程：比进程更小的执行单位，一个进程可以启动多个线程，每条线程并行执行不同的任务。</li></ul><h2 id="❷并行-amp-并发"><a href="#❷并行-amp-并发" class="headerlink" title="❷并行&amp;并发"></a>❷并行&amp;并发</h2><ul><li>并行（Parallel）：在同一时刻，有多个指令在多个 CPU 上同时执行。</li><li>并发（Concurrent）：在同一时刻，有多个指令在单个 CPU 上交替执行。微观串行，宏观并行</li></ul><h2 id="❸同步-amp-异步"><a href="#❸同步-amp-异步" class="headerlink" title="❸同步&amp;异步"></a>❸同步&amp;异步</h2><ul><li>同步（sync）：需要等待结果返回，才能继续运行</li><li>异步（Async）：不需要等待结果返回，就能继续运行</li></ul><h1 id="①线程"><a href="#①线程" class="headerlink" title="①线程"></a>①线程</h1><h2 id="❶线程创建"><a href="#❶线程创建" class="headerlink" title="❶线程创建"></a>❶线程创建</h2><h3 id="Thread"><a href="#Thread" class="headerlink" title="Thread"></a>Thread</h3><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Slf4j</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ThreadCreate</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>        Thread t1 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token string">"t1"</span><span class="token punctuation">)</span><span class="token punctuation">{</span>            <span class="token annotation punctuation">@Override</span>            <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">run</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                log<span class="token punctuation">.</span><span class="token function">debug</span><span class="token punctuation">(</span><span class="token string">"hello"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 启动线程</span>        t1<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        log<span class="token punctuation">.</span><span class="token function">debug</span><span class="token punctuation">(</span><span class="token string">"do other things ..."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="Runnable"><a href="#Runnable" class="headerlink" title="Runnable"></a>Runnable</h3><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Slf4j</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ThreadCreate</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>        Runnable task2 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Runnable</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token annotation punctuation">@Override</span>            <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">run</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>                log<span class="token punctuation">.</span><span class="token function">debug</span><span class="token punctuation">(</span><span class="token string">"hello"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span><span class="token punctuation">;</span>        Thread t2 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span>task2<span class="token punctuation">,</span> <span class="token string">"t2"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 启动线程</span>        t2<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        log<span class="token punctuation">.</span><span class="token function">debug</span><span class="token punctuation">(</span><span class="token string">"do other things ..."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">//写法2</span>Thread t4 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Runnable</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">run</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        log<span class="token punctuation">.</span><span class="token function">debug</span><span class="token punctuation">(</span><span class="token string">"hello"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token punctuation">}</span><span class="token punctuation">,</span><span class="token string">"t4"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>t4<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>Java 8 以后可以使用 lambda 精简代码</p><pre class="line-numbers language-java"><code class="language-java">Runnable task2 <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> log<span class="token punctuation">.</span><span class="token function">debug</span><span class="token punctuation">(</span><span class="token string">"hello"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>Thread t4 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span>task2<span class="token punctuation">,</span> <span class="token string">"t4"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>t4<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//写法2</span>Thread t4 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span>log<span class="token punctuation">.</span><span class="token function">debug</span><span class="token punctuation">(</span><span class="token string">"hello"</span><span class="token punctuation">)</span><span class="token punctuation">,</span><span class="token string">"t4"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>t4<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="Callable"><a href="#Callable" class="headerlink" title="Callable"></a>Callable</h3><p>FutureTask 能够接收 Callable 类型的参数，用来处理有返回结果的情况</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Slf4j</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ThreadCreate</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>        FutureTask<span class="token operator">&lt;</span>Integer<span class="token operator">></span> task3 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">FutureTask</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Callable</span><span class="token operator">&lt;</span>Integer<span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token annotation punctuation">@Override</span>                <span class="token keyword">public</span> Integer <span class="token function">call</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">throws</span> Exception <span class="token punctuation">{</span>                    log<span class="token punctuation">.</span><span class="token function">debug</span><span class="token punctuation">(</span><span class="token string">"hello"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                    <span class="token keyword">return</span> <span class="token number">100</span><span class="token punctuation">;</span>                <span class="token punctuation">}</span>            <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        Thread t5 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span>task3<span class="token punctuation">,</span> <span class="token string">"t5"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        t5<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 主线程阻塞，同步等待 task 执行完毕的结果</span>        <span class="token comment" spellcheck="true">// 获取call方法返回的结果（正常/异常结果）</span>        Integer result <span class="token operator">=</span> task3<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>         log<span class="token punctuation">.</span><span class="token function">debug</span><span class="token punctuation">(</span><span class="token string">"结果是:{}"</span><span class="token punctuation">,</span> result<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">//简写</span>FutureTask<span class="token operator">&lt;</span>Integer<span class="token operator">></span> task3 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">FutureTask</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span>  log<span class="token punctuation">.</span><span class="token function">debug</span><span class="token punctuation">(</span><span class="token string">"hello"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">return</span> <span class="token number">100</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>Thread t5 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span>task3<span class="token punctuation">,</span> <span class="token string">"t5"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>t5<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 主线程阻塞，同步等待 task 执行完毕的结果</span><span class="token comment" spellcheck="true">// 获取call方法返回的结果（正常/异常结果）</span>Integer result <span class="token operator">=</span> task3<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>log<span class="token punctuation">.</span><span class="token function">debug</span><span class="token punctuation">(</span><span class="token string">"结果是:{}"</span><span class="token punctuation">,</span> result<span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="❷线程方法"><a href="#❷线程方法" class="headerlink" title="❷线程方法"></a>❷线程方法</h2><h3 id="API"><a href="#API" class="headerlink" title="API"></a>API</h3><table><thead><tr><th>方法</th><th>说明</th></tr></thead><tbody><tr><td>public void start()</td><td>启动一个新线程，Java虚拟机调用此线程的 run 方法</td></tr><tr><td>public void run()</td><td>线程启动后调用该方法</td></tr><tr><td>public void setName(String name)</td><td>给当前线程取名字</td></tr><tr><td>public void getName()</td><td>获取当前线程的名字<br />线程存在默认名称：子线程是 Thread-索引，主线程是 main</td></tr><tr><td>public static Thread currentThread()</td><td>获取当前线程对象</td></tr><tr><td>public static void sleep(long time)</td><td>让当前线程休眠多少毫秒再继续执行<br /><strong>Thread.sleep(0)</strong> : 让操作系统立刻重新进行一次 CPU 竞争</td></tr><tr><td>public static native void yield()</td><td>提示线程调度器让出当前线程对 CPU 的使用</td></tr><tr><td>public final int getPriority()</td><td>返回此线程的优先级</td></tr><tr><td>public final void setPriority(int priority)</td><td>更改此线程的优先级，常用 1 5 10</td></tr><tr><td>public void interrupt()</td><td>中断这个线程，异常处理机制</td></tr><tr><td>public static boolean interrupted()</td><td>判断当前线程是否被打断，清除打断标记</td></tr><tr><td>public boolean isInterrupted()</td><td>判断当前线程是否被打断，不清除打断标记</td></tr><tr><td>public final void join()</td><td>等待这个线程结束</td></tr><tr><td>public final void join(long millis)</td><td>等待这个线程死亡 millis 毫秒，0 意味着永远等待</td></tr><tr><td>public final native boolean isAlive()</td><td>线程是否存活（还没有运行完毕）</td></tr><tr><td>public final void setDaemon(boolean on)</td><td>将此线程标记为守护线程或用户线程</td></tr></tbody></table><h3 id="run-amp-start"><a href="#run-amp-start" class="headerlink" title="run &amp; start"></a>run &amp; start</h3><ul><li>直接调用 run 是在主线程中执行了 run，没有启动新的线程</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Slf4j</span><span class="token punctuation">(</span>topic <span class="token operator">=</span> <span class="token string">"test"</span><span class="token punctuation">)</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">RunAndStart</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>        Thread t1 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span>            log<span class="token punctuation">.</span><span class="token function">debug</span><span class="token punctuation">(</span><span class="token string">"测试！！！"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token string">"t1"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        t1<span class="token punctuation">.</span><span class="token function">run</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        log<span class="token punctuation">.</span><span class="token function">debug</span><span class="token punctuation">(</span><span class="token string">"do other things ..."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-java"><code class="language-java"><span class="token number">16</span><span class="token operator">:</span><span class="token number">09</span><span class="token operator">:</span><span class="token number">05.495</span> <span class="token punctuation">[</span>main<span class="token punctuation">]</span> DEBUG test <span class="token operator">-</span> 测试！！！<span class="token number">16</span><span class="token operator">:</span><span class="token number">09</span><span class="token operator">:</span><span class="token number">05.496</span> <span class="token punctuation">[</span>main<span class="token punctuation">]</span> DEBUG test <span class="token operator">-</span> <span class="token keyword">do</span> other things <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><ul><li>使用 start 是启动新的线程，通过新的线程间接执行 run 中的代码</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Slf4j</span><span class="token punctuation">(</span>topic <span class="token operator">=</span> <span class="token string">"test"</span><span class="token punctuation">)</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">RunAndStart</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>        Thread t1 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span>            log<span class="token punctuation">.</span><span class="token function">debug</span><span class="token punctuation">(</span><span class="token string">"测试！！！"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token string">"t1"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        t1<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        log<span class="token punctuation">.</span><span class="token function">debug</span><span class="token punctuation">(</span><span class="token string">"do other things ..."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-java"><code class="language-java"><span class="token number">16</span><span class="token operator">:</span><span class="token number">10</span><span class="token operator">:</span><span class="token number">24.051</span> <span class="token punctuation">[</span>main<span class="token punctuation">]</span> DEBUG test <span class="token operator">-</span> <span class="token keyword">do</span> other things <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token number">16</span><span class="token operator">:</span><span class="token number">10</span><span class="token operator">:</span><span class="token number">24.051</span> <span class="token punctuation">[</span>t1<span class="token punctuation">]</span> DEBUG test <span class="token operator">-</span> 测试！！！<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><h3 id="sleep-amp-yield"><a href="#sleep-amp-yield" class="headerlink" title="sleep &amp; yield"></a>sleep &amp; yield</h3><p>sleep：</p><ul><li><p>调用 sleep 会让当前线程从 <code>Running</code> 进入 <code>Timed Waiting</code> 状态（阻塞）</p></li><li><p>sleep() 方法的过程中，<strong>线程不会释放对象锁</strong></p></li><li><p>其它线程可以使用 interrupt 方法打断正在睡眠的线程，这时 sleep 方法会抛出 InterruptedException</p></li><li><p>睡眠结束后的线程未必会立刻得到执行，需要抢占 CPU</p></li><li><p>建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性</p><pre class="line-numbers language-java"><code class="language-java">TimeUnit<span class="token punctuation">.</span>SECONDS<span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre></li></ul><p>yield：</p><ul><li>调用 yield 会让提示线程调度器让出当前线程对 CPU 的使用</li><li>具体的实现依赖于操作系统的任务调度器</li><li><strong>会放弃 CPU 资源，锁资源不会释放</strong></li></ul><h3 id="join"><a href="#join" class="headerlink" title="join"></a>join</h3><blockquote><p>等待这个线程结束</p></blockquote><p>原理：调用者轮询检查线程 alive 状态，t1.join() 等价于：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token keyword">synchronized</span> <span class="token keyword">void</span> <span class="token function">join</span><span class="token punctuation">(</span><span class="token keyword">long</span> millis<span class="token punctuation">)</span> <span class="token keyword">throws</span> InterruptedException <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 调用者线程进入 thread 的 waitSet 等待, 直到当前线程运行结束</span>    <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token function">isAlive</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token function">wait</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li><p>join 方法是被 synchronized 修饰的，本质上是一个对象锁，其内部的 wait 方法调用也是释放锁的，但是<strong>释放的是当前的线程对象锁，而不是外面的锁</strong></p></li><li><p>当调用某个线程的 join 方法后，该线程抢占到 CPU 资源，就不再释放，直到线程执行完毕</p></li></ul><p>线程同步：</p><ul><li>join 实现线程同步，因为会阻塞等待另一个线程的结束，才能继续向下运行<ul><li>需要外部共享变量，不符合面向对象封装的思想</li><li>必须等待线程结束，不能配合线程池使用</li></ul></li><li>Future 实现同步：get() 方法阻塞等待执行结果<ul><li>main 线程接收结果</li><li>get 方法是让调用线程同步等待</li></ul></li></ul><h3 id="interrupt"><a href="#interrupt" class="headerlink" title="interrupt"></a>interrupt</h3><h4 id="打断线程"><a href="#打断线程" class="headerlink" title="打断线程"></a>打断线程</h4><p><code>interrupt()</code>：打断当前线程，异常处理机制</p><p><code>interrupted()</code>：判断当前线程是否被打断，打断返回 true，<strong>清除打断标记</strong></p><p><code>isInterrupted()</code>：判断当前线程是否被打断，不清除打断标记</p><ul><li>sleep、wait、join 方法都会让线程进入阻塞状态，打断线程<strong>会清空打断状态</strong>（false）</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token keyword">throws</span> InterruptedException <span class="token punctuation">{</span>    Thread t1 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">></span><span class="token punctuation">{</span>        <span class="token keyword">try</span> <span class="token punctuation">{</span>            Thread<span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">1000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">InterruptedException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span>            e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token string">"t1"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    t1<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    Thread<span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">500</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    t1<span class="token punctuation">.</span><span class="token function">interrupt</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">" 打断状态: "</span> <span class="token operator">+</span> t1<span class="token punctuation">.</span><span class="token function">isInterrupted</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 打断状态: false</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>打断正常运行的线程：不会清空打断状态（true）</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token keyword">throws</span> Exception <span class="token punctuation">{</span>    Thread t2 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">></span><span class="token punctuation">{</span>        <span class="token keyword">while</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            Thread current <span class="token operator">=</span> Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">boolean</span> interrupted <span class="token operator">=</span> current<span class="token punctuation">.</span><span class="token function">isInterrupted</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">if</span><span class="token punctuation">(</span>interrupted<span class="token punctuation">)</span> <span class="token punctuation">{</span>                System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">" 打断状态: "</span> <span class="token operator">+</span> interrupted<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//打断状态: true</span>                <span class="token keyword">break</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token string">"t2"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    t2<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    Thread<span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">500</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    t2<span class="token punctuation">.</span><span class="token function">interrupt</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="打断-park"><a href="#打断-park" class="headerlink" title="打断 park"></a>打断 park</h4><p>park 作用类似 sleep，打断 park 线程，不会清空打断状态（true）</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token keyword">throws</span> Exception <span class="token punctuation">{</span>    Thread t1 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"park..."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        LockSupport<span class="token punctuation">.</span><span class="token function">park</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"unpark..."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"打断状态："</span> <span class="token operator">+</span> Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">isInterrupted</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//打断状态：true</span>    <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token string">"t1"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    t1<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    Thread<span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">2000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    t1<span class="token punctuation">.</span><span class="token function">interrupt</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>如果打断标记已经是 true, 则 park 会失效</p><pre class="line-numbers language-java"><code class="language-java">LockSupport<span class="token punctuation">.</span><span class="token function">park</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"unpark..."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>LockSupport<span class="token punctuation">.</span><span class="token function">park</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//失效，不会阻塞</span>System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"unpark..."</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//和上一个unpark同时执行</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>可以修改获取打断状态方法，使用 <code>Thread.interrupted()</code>，清除打断标记</p><h4 id="终止模式"><a href="#终止模式" class="headerlink" title="终止模式"></a>终止模式</h4><p>终止模式之两阶段终止模式：Two Phase Termination</p><p>目标：在一个线程 T1 中如何优雅终止线程 T2？优雅指的是给 T2 一个后置处理器</p><p>错误思想：</p><ul><li>使用线程对象的 stop() 方法停止线程：stop 方法会真正杀死线程，如果这时线程锁住了共享资源，当它被杀死后就再也没有机会释放锁，其它线程将永远无法获取锁</li><li>使用 System.exit(int) 方法停止线程：目的仅是停止一个线程，但这种做法会让整个程序都停止</li></ul><p>两阶段终止模式图示：</p><img src="https://img.jwt1399.top/img/202212282140792.png" style="zoom: 67%;" /><p>打断线程可能在任何时间，所以需要考虑在任何时刻被打断的处理方法：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Test</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token keyword">throws</span> InterruptedException <span class="token punctuation">{</span>        TwoPhaseTermination tpt <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">TwoPhaseTermination</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        tpt<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        Thread<span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">3500</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        tpt<span class="token punctuation">.</span><span class="token function">stop</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token keyword">class</span> <span class="token class-name">TwoPhaseTermination</span> <span class="token punctuation">{</span>    <span class="token keyword">private</span> Thread monitor<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 启动监控线程</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        monitor <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Runnable</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token annotation punctuation">@Override</span>            <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">run</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                    Thread thread <span class="token operator">=</span> Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                    <span class="token keyword">if</span> <span class="token punctuation">(</span>thread<span class="token punctuation">.</span><span class="token function">isInterrupted</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"后置处理"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                        <span class="token keyword">break</span><span class="token punctuation">;</span>                    <span class="token punctuation">}</span>                    <span class="token keyword">try</span> <span class="token punctuation">{</span>                        Thread<span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">1000</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 睡眠</span>                        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"执行监控记录"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 在此被打断不会异常</span>                    <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">InterruptedException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token comment" spellcheck="true">// 在睡眠期间被打断，进入异常处理的逻辑</span>                        e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                        <span class="token comment" spellcheck="true">// 重新设置打断标记，打断 sleep 会清除打断状态</span>                        thread<span class="token punctuation">.</span><span class="token function">interrupt</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                    <span class="token punctuation">}</span>                <span class="token punctuation">}</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        monitor<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">// 停止监控线程</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">stop</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        monitor<span class="token punctuation">.</span><span class="token function">interrupt</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="daemon"><a href="#daemon" class="headerlink" title="daemon"></a>daemon</h3><pre class="line-numbers language-java"><code class="language-java">Thread t <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">run</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"running"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 设置该线程为守护线程</span>t<span class="token punctuation">.</span><span class="token function">setDaemon</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span>t<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>用户线程：平常创建的普通线程</p><p>守护线程：服务于用户线程，只要其它非守护线程运行结束了，即使守护线程代码没有执行完，也会强制结束。</p><p>常见的守护线程：</p><ul><li>垃圾回收器线程就是一种守护线程</li><li>Tomcat 中的 Acceptor 和 Poller 线程都是守护线程，所以 Tomcat 接收到 shutdown 命令后，不会等待它们处理完当前请求</li></ul><h2 id="❸线程原理"><a href="#❸线程原理" class="headerlink" title="❸线程原理"></a>❸线程原理</h2><p>Java 虚拟机栈（Java Virtual Machine Stacks）：每个线程启动后，虚拟机就会为其分配一块栈内存</p><ul><li>每个栈由多个栈帧（Frame）组成，对应着每次方法调用时所占用的内存</li><li>每个线程只能有一个活动栈帧，对应着当前正在执行的那个方法</li></ul><p>线程上下文切换（Thread Context Switch）：一些原因导致 CPU 不再执行当前线程，转而执行另一个线程</p><ul><li>线程的 CPU 时间片用完</li><li>垃圾回收</li><li>有更高优先级的线程需要运行</li><li>线程自己调用了 sleep、yield、wait、join、park 等方法</li></ul><p>程序计数器（Program Counter Register）：记录正在执行的字节码指令地址，是线程私有的</p><p>当 Context Switch 发生时，需要由操作系统保存当前线程的状态（PCB 中），并恢复另一个线程的状态，包括程序计数器、虚拟机栈中每个栈帧的信息，如局部变量、操作数栈、返回地址等</p><p>Java 中 main 方法启动的是一个进程也是一个主线程，main 方法里面的其他线程均为子线程，main 线程是这些线程的父线程</p><h2 id="❹线程状态"><a href="#❹线程状态" class="headerlink" title="❹线程状态"></a>❹线程状态</h2><p>操作系统进程的状态（5种）：创建态（new）、就绪态（ready）、运行态（running）、阻塞态（waiting）、终止态（terminated）</p><p><img src="https://img.jwt1399.top/img/202211251026928.png"></p><p>在 Java API 中 <code>java.lang.Thread.State</code> 这个枚举中给出了六种线程状态：</p><table><thead><tr><th>线程状态</th><th>导致状态发生条件</th></tr></thead><tbody><tr><td>NEW（新建）</td><td>线程刚被创建，但是并未启动，还没调用 start 方法，只有线程对象，没有线程特征</td></tr><tr><td>Runnable（可运行）</td><td>线程可以在 Java 虚拟机中运行的状态，可能正在运行自己代码，也可能没有，这取决于操作系统处理器，调用了 t.start() 方法：就绪（经典叫法）</td></tr><tr><td>Blocked（阻塞）</td><td>当一个线程试图获取一个对象锁，而该对象锁被其他的线程持有，则该线程进入 Blocked 状态；当该线程持有锁时，该线程将变成 Runnable 状态</td></tr><tr><td>Waiting（无限等待）</td><td>一个线程在等待另一个线程执行一个（唤醒）动作时，该线程进入 Waiting 状态，进入这个状态后不能自动唤醒，必须等待另一个线程调用 notify 或者 notifyAll 方法才能唤醒</td></tr><tr><td>Timed Waiting （限期等待）</td><td>有几个方法有超时参数，调用将进入 Timed Waiting 状态，这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有 Thread.sleep 、Object.wait</td></tr><tr><td>Teminated（结束）</td><td>run 方法正常退出而死亡，或者因为没有捕获的异常终止了 run 方法而死亡</td></tr></tbody></table><h2 id="❺线程状态转换"><a href="#❺线程状态转换" class="headerlink" title="❺线程状态转换"></a>❺线程状态转换</h2><img src="https://img.jwt1399.top/img/202212191915210.png"  /><p>①NEW → RUNNABLE：</p><ul><li>当调用 <code>t.start()</code> 方法时，t 线程从 NEW → RUNNABLE</li></ul><p>②RUNNABLE <strong>⇆</strong>  WAITING</p><p>t 线程用 <code>synchronized(obj)</code> 获取了对象锁后</p><ul><li><p>调用 <code>obj.wait()</code> 方法时，t 线程从 RUNNABLE → WAITING</p></li><li><p>调用 <code>obj.notify()</code> ， <code>obj.notifyAll()</code> ， <code>t.interrupt()</code> 时</p><ul><li><p>竞争锁成功，t 线程从 WAITING → RUNNABLE</p></li><li><p>竞争锁失败，t 线程从 WAITING → BLOCKED</p></li></ul></li></ul><p>③RUNNABLE <strong>⇆</strong>  WAITING</p><ul><li>当前线程调用 <code>t.join()</code> 方法时，当前线程从 RUNNABLE → WAITING <ul><li>注意是当前线程在 t 线程对象的监视器上等待</li></ul></li><li>t 线程运行结束或调用了当前线程的 <code>interrupt()</code> 时，当前线程从 WAITING → RUNNABLE</li></ul><p>④RUNNABLE <strong>⇆</strong>  WAITING</p><ul><li>当前线程调用 <code>LockSupport.park()</code> 方法会让当前线程从 RUNNABLE → WAITING</li><li>调用 <code>LockSupport.unpark(目标线程)</code> 或调用了线程 的 <code>interrupt()</code> ，会让目标线程从 WAITING →RUNNABLE</li></ul><p>⑤RUNNABLE <strong>⇆</strong> TIMED_WAITING</p><p>t 线程用 synchronized(obj) 获取了对象锁后</p><ul><li>调用 <code>obj.wait(long n)</code> 方法时，t 线程从 RUNNABLE → TIMED_WAITING</li><li>t 线程等待时间超过了 n 毫秒，或调用 <code>obj.notify()</code> ， <code>obj.notifyAll()</code> ， <code>t.interrupt()</code> 时<ul><li>竞争锁成功，t 线程从 TIMED_WAITING → RUNNABLE</li><li>竞争锁失败，t 线程从 TIMED_WAITING → BLOCKED</li></ul></li></ul><p>⑥RUNNABLE <strong>⇆</strong>  TIMED_WAITING</p><ul><li>当前线程调用 <code>t.join(long n)</code> 方法时，当前线程从 RUNNABLE → TIMED_WAITING<ul><li>注意是当前线程在t 线程对象的监视器上等待</li></ul></li><li>当前线程等待时间超过了 n 毫秒或t 线程运行结束，或调用了当前线程的 <code>interrupt()</code> 时，当前线程从TIMED_WAITING → RUNNABLE</li></ul><p>⑦RUNNABLE <strong>⇆</strong> TIMED_WAITING</p><ul><li>当前线程调用 <code>Thread.sleep(long n)</code> ，当前线程从 RUNNABLE → TIMED_WAITING</li><li>当前线程等待时间超过了 n 毫秒，当前线程从 TIMED_WAITING → RUNNABLE</li></ul><p>⑧RUNNABLE <strong>⇆</strong>  TIMED_WAITING</p><ul><li>当前线程调用 <code>LockSupport.parkNanos(long nanos)</code> 或 <code>LockSupport.parkUntil(long millis)</code> 时，当前线程从 RUNNABLE → TIMED_WAITING</li><li>调用 <code>LockSupport.unpark(目标线程)</code> 或调用了线程 的 <code>interrupt()</code> ，或是等待超时，会让目标线程从TIMED_WAITING → RUNNABLE</li></ul><p>⑨RUNNABLE <strong>⇆</strong>  BLOCKED</p><ul><li><p>t 线程用 <code>synchronized(obj)</code> 获取了对象锁时，如果竞争失败，从 RUNNABLE → BLOCKED</p></li><li><p>持 obj 锁线程的同步代码块执行完毕，会唤醒该对象上所有 BLOCKED 的线程重新竞争</p><ul><li>如果其中 t 线程竞争成功，从 BLOCKED → RUNNABLE </li><li>其它失败的线程仍然 BLOCKED</li></ul></li></ul><p>⑩RUNNABLE <strong>⇆</strong>  TERMINATED</p><ul><li>当前线程所有代码运行完毕，进入 TERMINATED</li></ul><p><img src="https://img.jwt1399.top/img/202212281939521.png" alt="线程状态转移图"></p><p><strong>总结</strong></p><ul><li><p>NEW → RUNNABLE：当调用 t.start() 方法时，由 NEW → RUNNABLE</p></li><li><p>RUNNABLE ⇆ WAITING：</p><ul><li><p>调用 obj.wait() 方法时，t 线程从 RUNNABLE → WAITING</p></li><li><p>调用 obj.notify()、obj.notifyAll()、t.interrupt()：</p></li><li><p>竞争锁成功，线程从 WAITING → RUNNABLE</p><ul><li>竞争锁失败，线程从 WAITING → BLOCKED</li></ul></li><li><p>当前线程调用 t.join() 方法，当前线程从 RUNNABLE → WAITING </p></li><li><p>当前线程调用 LockSupport.park() 方法，当前线程从 RUNNABLE → WAITING</p></li></ul></li><li><p>RUNNABLE ⇆ TIMED_WAITING：调用 obj.wait(long n) 方法、调用 t.join(long n) 方法、调用 Thread.sleep(long n)</p></li><li><p>RUNNABLE ⇆ BLOCKED：线程用 synchronized(obj) 获取了对象锁时竞争失败</p></li></ul><h2 id="❻线程查看命令"><a href="#❻线程查看命令" class="headerlink" title="❻线程查看命令"></a>❻线程查看命令</h2><p>windows</p><ul><li>任务管理器可以查看进程和线程数，也可以用来杀死进程</li><li><code>tasklist</code> 查看进程</li><li><code>taskkill</code> 杀死进程</li></ul><p>linux</p><ul><li><p><code>ps -ef</code> 查看所有进程</p></li><li><p><code>ps -fT -p &lt;PID&gt;</code> 查看某个进程（PID）的所有线程</p></li><li><p><code>kill</code> 杀死进程</p></li><li><p><code>top</code> 按大写 H 切换是否显示线程</p></li><li><p><code>top -H -p &lt;PID&gt;</code> 查看某个进程（PID）的所有线程</p></li></ul><p>Java</p><ul><li><p><code>jps</code> 命令查看所有 Java 进程</p></li><li><p><code>jstack &lt;PID&gt;</code> 查看某个 Java 进程（PID）的所有线程状态</p></li><li><p><code>jconsole</code> 查看某个 Java 进程中线程的运行情况（图形界面）</p><pre class="line-numbers language-bash"><code class="language-bash"><span class="token comment" spellcheck="true"># jconsole 远程监控配置</span>java -Djava.rmi.server.hostname<span class="token operator">=</span><span class="token variable"><span class="token variable">`</span>ip地址<span class="token variable">`</span></span> -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port<span class="token operator">=</span><span class="token variable"><span class="token variable">`</span>连接端口<span class="token variable">`</span></span> -Dcom.sun.management.jmxremote.ssl<span class="token operator">=</span>是否安全连接 -Dcom.sun.management.jmxremote.authenticate<span class="token operator">=</span>是否认证 java类<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre></li></ul><h1 id="②同步"><a href="#②同步" class="headerlink" title="②同步"></a>②同步</h1><h2 id="❶临界区-Critical-Section"><a href="#❶临界区-Critical-Section" class="headerlink" title="❶临界区-Critical Section"></a>❶临界区-Critical Section</h2><p>一段代码块内如果存在对<strong>共享资源</strong>的多线程读写操作，称这段代码块为<strong>临界区</strong></p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">static</span> <span class="token keyword">int</span> counter <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span><span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">increment</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true">// 临界区</span><span class="token punctuation">{</span>counter<span class="token operator">++</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">decrement</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true">// 临界区</span><span class="token punctuation">{</span>counter<span class="token operator">--</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="❷竞态条件-Race-Condition"><a href="#❷竞态条件-Race-Condition" class="headerlink" title="❷竞态条件-Race Condition"></a>❷竞态条件-Race Condition</h2><p>多个线程在临界区内执行，由于代码的执行序列不同而导致结果无法预测，称之为发生了竞态条件</p><p>一个程序运行多个线程是没有问题，多个线程读共享资源也没有问题，在多个线程对共享资源读写操作时发生指令交错，就会出现问题</p><p>为了避免临界区的竞态条件发生（解决线程安全问题）：</p><ul><li>阻塞式的解决方案：synchronized，lock</li><li>非阻塞式的解决方案：原子变量</li></ul><h2 id="❸synchronized"><a href="#❸synchronized" class="headerlink" title="❸synchronized"></a>❸synchronized</h2><h3 id="锁使用"><a href="#锁使用" class="headerlink" title="锁使用"></a>锁使用</h3><blockquote><p>synchronized 是可重入、不公平的重量级锁</p></blockquote><p><strong>synchronized：对象锁，保证了临界区内代码的原子性</strong>，采用互斥的方式让同一时刻至多只有一个线程能持有对象锁，其它线程获取这个对象锁时会阻塞，保证拥有锁的线程可以安全的执行临界区内的代码，不用担心线程上下文切换</p><p>互斥和同步都可以采用 synchronized 关键字来完成，区别：</p><ul><li>互斥是保证临界区的竞态条件发生，同一时刻只能有一个线程执行临界区代码</li><li>同步是由于线程执行的先后、顺序不同、需要一个线程等待其它线程运行到某个点</li></ul><h4 id="同步代码块"><a href="#同步代码块" class="headerlink" title="同步代码块"></a>同步代码块</h4><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">synchronized</span><span class="token punctuation">(</span>锁对象<span class="token punctuation">)</span><span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 访问共享资源的核心代码</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>实例：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">demo</span> <span class="token punctuation">{</span>    <span class="token keyword">static</span> <span class="token keyword">int</span> counter <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">//static修饰，则元素是属于类本身的，不属于对象  ，与类一起加载一次，只有一个</span>    <span class="token keyword">static</span> <span class="token keyword">final</span> Object obj <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Object</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token keyword">throws</span> InterruptedException <span class="token punctuation">{</span>        Thread t1 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span>            <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> <span class="token number">5000</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token keyword">synchronized</span> <span class="token punctuation">(</span>obj<span class="token punctuation">)</span> <span class="token punctuation">{</span>                    counter<span class="token operator">++</span><span class="token punctuation">;</span>                <span class="token punctuation">}</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token string">"t1"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        Thread t2 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span>            <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> <span class="token number">5000</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token keyword">synchronized</span> <span class="token punctuation">(</span>obj<span class="token punctuation">)</span> <span class="token punctuation">{</span>                    counter<span class="token operator">--</span><span class="token punctuation">;</span>                <span class="token punctuation">}</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token string">"t2"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        t1<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        t2<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        t1<span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        t2<span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>counter<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="同步方法"><a href="#同步方法" class="headerlink" title="同步方法"></a>同步方法</h4><p>解决线程安全问题的核心方法是使用锁，每次只能一个线程进入访问</p><p>synchronized 修饰的方法的不具备继承性，所以子类是线程不安全的</p><p>如果子类的方法也被 synchronized 修饰，两个锁对象其实是一把锁，而且是<strong>子类对象作为锁</strong></p><p>用法：直接给方法加上一个修饰符 synchronized</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">//同步方法</span>修饰符 <span class="token keyword">synchronized</span> 返回值类型 方法名<span class="token punctuation">(</span>方法参数<span class="token punctuation">)</span> <span class="token punctuation">{</span>     方法体；<span class="token punctuation">}</span><span class="token comment" spellcheck="true">//同步静态方法</span>修饰符 <span class="token keyword">static</span> <span class="token keyword">synchronized</span> 返回值类型 方法名<span class="token punctuation">(</span>方法参数<span class="token punctuation">)</span> <span class="token punctuation">{</span>     方法体；<span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li><p>如果方法是实例方法：同步方法默认用 this 作为的锁对象</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">synchronized</span> <span class="token keyword">void</span> <span class="token function">test</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">}</span> <span class="token comment" spellcheck="true">//等价于</span><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">test</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">synchronized</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>如果方法是静态方法：同步方法默认用类名 .class 作为的锁对象</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Test</span><span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">synchronized</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">test</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">//等价于</span><span class="token keyword">class</span> <span class="token class-name">Test</span><span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">test</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">synchronized</span><span class="token punctuation">(</span>Test<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li></ul><h4 id="线程八锁"><a href="#线程八锁" class="headerlink" title="线程八锁"></a>线程八锁</h4><blockquote><p>线程八锁就是考察 synchronized 锁住的是哪个对象</p></blockquote><p>说明：主要关注锁住的对象是不是同一个</p><ul><li>锁住类对象，所有类的实例的方法都是安全的，类的所有实例都相当于同一把锁</li><li>锁住 this 对象，只有在当前实例对象的线程内是安全的，如果有多个实例就不安全</li></ul><p>线程不安全：因为锁住的不是同一个对象，线程 1 调用 a 方法锁住的类对象和线程 2 调用 b 方法锁住的 n2 对象，不是同一个对象</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Number</span><span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">synchronized</span> <span class="token keyword">void</span> <span class="token function">a</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>                Thread<span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">1000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"1"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">public</span> <span class="token keyword">synchronized</span> <span class="token keyword">void</span> <span class="token function">b</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"2"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>    Number n1 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Number</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    Number n2 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Number</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">></span><span class="token punctuation">{</span> n1<span class="token punctuation">.</span><span class="token function">a</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">></span><span class="token punctuation">{</span> n2<span class="token punctuation">.</span><span class="token function">b</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>线程安全：因为 n1 调用 a() 方法，锁住的是类对象，n2 调用 b() 方法，锁住的也是类对象，所以线程安全</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Number</span><span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">synchronized</span> <span class="token keyword">void</span> <span class="token function">a</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>                Thread<span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">1000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"1"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">synchronized</span> <span class="token keyword">void</span> <span class="token function">b</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"2"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>    Number n1 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Number</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    Number n2 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Number</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">></span><span class="token punctuation">{</span> n1<span class="token punctuation">.</span><span class="token function">a</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">></span><span class="token punctuation">{</span> n2<span class="token punctuation">.</span><span class="token function">b</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="锁原理"><a href="#锁原理" class="headerlink" title="锁原理"></a>锁原理</h3><h4 id="Monitor"><a href="#Monitor" class="headerlink" title="Monitor"></a>Monitor</h4><p>Monitor 被翻译为监视器或管程：每个 Java 对象都可以关联一个 Monitor 对象，Monitor 也是 class，其<strong>实例存储在堆中</strong>，如果使用 synchronized 给对象上锁（重量级）之后，该对象头的 <code>Mark Word</code> 中就被设置指向 Monitor 对象的指针，这就是重量级锁</p><p><code>Mark Word</code>：用于存储对象自身的运行时数据， 如哈希码（HashCode）、GC 分代年龄、锁状态标志（最后两位）、线程持有的锁、偏向线程ID、偏向时间戳等等。最后两位是<strong>锁标志位</strong></p><p>32 位虚拟机 Mark Word</p><pre class="line-numbers language-ruby"><code class="language-ruby"><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">-</span><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">|</span><span class="token operator">|</span>                    <span class="token constant">Mark</span> <span class="token function">Word</span> <span class="token punctuation">(</span><span class="token number">32</span> bits<span class="token punctuation">)</span>                <span class="token operator">|</span>        <span class="token constant">State</span>       <span class="token operator">|</span><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">-</span><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">|</span><span class="token operator">|</span>              hashcode<span class="token punctuation">:</span><span class="token number">25</span> <span class="token operator">|</span> age<span class="token punctuation">:</span><span class="token number">4</span> <span class="token operator">|</span> biased_lock<span class="token punctuation">:</span><span class="token number">0</span> <span class="token operator">|</span> <span class="token number">01</span> <span class="token operator">|</span>        <span class="token constant">Normal</span>      <span class="token operator">|</span><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">-</span><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">|</span><span class="token operator">|</span>      thread<span class="token punctuation">:</span><span class="token number">23</span> <span class="token operator">|</span> epoch<span class="token punctuation">:</span><span class="token number">2</span> <span class="token operator">|</span> age<span class="token punctuation">:</span><span class="token number">4</span> <span class="token operator">|</span> biased_lock<span class="token punctuation">:</span><span class="token number">1</span> <span class="token operator">|</span> <span class="token number">01</span> <span class="token operator">|</span>        <span class="token constant">Biased</span>      <span class="token operator">|</span><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">-</span><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">|</span><span class="token operator">|</span>                            ptr_to_lock_record<span class="token punctuation">:</span><span class="token number">30</span> <span class="token operator">|</span> <span class="token number">00</span> <span class="token operator">|</span> <span class="token constant">Lightweight</span> <span class="token constant">Locked</span> <span class="token operator">|</span><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">-</span><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">|</span><span class="token operator">|</span>                    ptr_to_heavyweight_monitor<span class="token punctuation">:</span><span class="token number">30</span> <span class="token operator">|</span> <span class="token number">10</span> <span class="token operator">|</span> <span class="token constant">Heavyweight</span> <span class="token constant">Locked</span> <span class="token operator">|</span><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">-</span><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">|</span><span class="token operator">|</span>                                                  <span class="token operator">|</span> <span class="token number">11</span> <span class="token operator">|</span>    <span class="token constant">Marked</span> <span class="token keyword">for</span> <span class="token constant">GC</span>   <span class="token operator">|</span><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">-</span><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">|</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>64 位虚拟机 Mark Word</p><pre class="line-numbers language-ruby"><code class="language-ruby"><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">|</span><span class="token operator">|</span>                      <span class="token constant">Mark</span> <span class="token function">Word</span> <span class="token punctuation">(</span><span class="token number">64</span> bits<span class="token punctuation">)</span>                           <span class="token operator">|</span>       <span class="token constant">State</span>        <span class="token operator">|</span><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">|</span><span class="token operator">|</span>    unused<span class="token punctuation">:</span><span class="token number">25</span> <span class="token operator">|</span> hashcode<span class="token punctuation">:</span><span class="token number">31</span> <span class="token operator">|</span> unused<span class="token punctuation">:</span><span class="token number">1</span> <span class="token operator">|</span> age<span class="token punctuation">:</span><span class="token number">4</span> <span class="token operator">|</span> biased_lock<span class="token punctuation">:</span><span class="token number">0</span> <span class="token operator">|</span> <span class="token number">01</span> <span class="token operator">|</span>       <span class="token constant">Normal</span>       <span class="token operator">|</span><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">|</span><span class="token operator">|</span>        thread<span class="token punctuation">:</span><span class="token number">54</span> <span class="token operator">|</span> epoch<span class="token punctuation">:</span><span class="token number">2</span> <span class="token operator">|</span> unused<span class="token punctuation">:</span><span class="token number">1</span> <span class="token operator">|</span> age<span class="token punctuation">:</span><span class="token number">4</span> <span class="token operator">|</span> biased_lock<span class="token punctuation">:</span><span class="token number">1</span> <span class="token operator">|</span> <span class="token number">01</span> <span class="token operator">|</span>       <span class="token constant">Biased</span>       <span class="token operator">|</span><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">|</span><span class="token operator">|</span>                                         ptr_to_lock_record<span class="token punctuation">:</span><span class="token number">62</span> <span class="token operator">|</span> <span class="token number">00</span> <span class="token operator">|</span> <span class="token constant">Lightweight</span> <span class="token constant">Locked</span> <span class="token operator">|</span><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">|</span><span class="token operator">|</span>                                 ptr_to_heavyweight_monitor<span class="token punctuation">:</span><span class="token number">62</span> <span class="token operator">|</span> <span class="token number">10</span> <span class="token operator">|</span> <span class="token constant">Heavyweight</span> <span class="token constant">Locked</span> <span class="token operator">|</span><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">|</span><span class="token operator">|</span>                                                               <span class="token operator">|</span> <span class="token number">11</span> <span class="token operator">|</span>    <span class="token constant">Marked</span> <span class="token keyword">for</span> <span class="token constant">GC</span>   <span class="token operator">|</span><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">|</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>工作流程：</p><ul><li>开始时 Monitor 中 Owner 为 null</li><li>当 Thread-2 执行 <code>synchronized(obj)</code> 就会将 Monitor 的所有者 Owner 置为 Thread-2，Monitor 中只能有一个 Owner，<strong>obj 对象的 Mark Word 指向 Monitor</strong>，把<strong>对象原有的 MarkWord 存入线程栈中的锁记录</strong>中（轻量级锁部分详解）<img src="../images/Java-JUC/JUC-Monitor工作原理1.png" style="zoom:67%;" /></li><li>在 Thread-2 上锁的过程，Thread-3、Thread-4、Thread-5 也执行 <code>synchronized(obj)</code>，就会进入 EntryList BLOCKED（双向链表）</li><li>Thread-2 执行完同步代码块的内容，根据 obj 对象头中 Monitor 地址寻找，设置 Owner 为空，把线程栈的锁记录中的对象头的值设置回 MarkWord</li><li>唤醒 EntryList 中等待的线程来竞争锁，竞争是<strong>非公平的</strong>，如果这时有新的线程想要获取锁，可能直接就抢占到了，阻塞队列的线程就会继续阻塞</li><li>WaitSet 中的 Thread-0，是以前获得过锁，但条件不满足进入 WAITING 状态的线程（wait-notify 机制）</li></ul><p><img src="https://img.jwt1399.top/img/202212271732344.png"></p><p>注意：</p><ul><li>synchronized 必须是进入同一个对象的 Monitor 才有上述的效果</li><li>不加 synchronized 的对象不会关联监视器，不遵从以上规则</li></ul><h4 id="字节码"><a href="#字节码" class="headerlink" title="字节码"></a>字节码</h4><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>    Object lock <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Object</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">synchronized</span> <span class="token punctuation">(</span>lock<span class="token punctuation">)</span> <span class="token punctuation">{</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"ok"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-java"><code class="language-java"><span class="token number">0</span><span class="token operator">:</span> <span class="token keyword">new</span>#<span class="token number">2</span><span class="token comment" spellcheck="true">// new Object</span><span class="token number">3</span><span class="token operator">:</span> dup<span class="token number">4</span><span class="token operator">:</span> invokespecial #<span class="token number">1</span> <span class="token comment" spellcheck="true">// invokespecial &lt;init>:()V，非虚方法</span><span class="token number">7</span><span class="token operator">:</span> astore_1 <span class="token comment" spellcheck="true">// lock引用 -> lock</span><span class="token number">8</span><span class="token operator">:</span> aload_1<span class="token comment" spellcheck="true">// lock （synchronized开始）</span><span class="token number">9</span><span class="token operator">:</span> dup<span class="token comment" spellcheck="true">// 一份用来初始化，一份用来引用</span><span class="token number">10</span><span class="token operator">:</span> astore_2 <span class="token comment" spellcheck="true">// lock引用 -> slot 2</span><span class="token number">11</span><span class="token operator">:</span> monitorenter <span class="token comment" spellcheck="true">// 【将 lock对象 MarkWord 置为 Monitor 指针】</span><span class="token number">12</span><span class="token operator">:</span> getstatic #<span class="token number">3</span><span class="token comment" spellcheck="true">// System.out</span><span class="token number">15</span><span class="token operator">:</span> ldc #<span class="token number">4</span><span class="token comment" spellcheck="true">// "ok"</span><span class="token number">17</span><span class="token operator">:</span> invokevirtual #<span class="token number">5</span> <span class="token comment" spellcheck="true">// invokevirtual println:(Ljava/lang/String;)V</span><span class="token number">20</span><span class="token operator">:</span> aload_2 <span class="token comment" spellcheck="true">// slot 2(lock引用)</span><span class="token number">21</span><span class="token operator">:</span> monitorexit <span class="token comment" spellcheck="true">// 【将 lock对象 MarkWord 重置, 唤醒 EntryList】</span><span class="token number">22</span><span class="token operator">:</span> <span class="token keyword">goto</span> <span class="token number">30</span><span class="token number">25</span><span class="token operator">:</span> astore_3 <span class="token comment" spellcheck="true">// any -> slot 3</span><span class="token number">26</span><span class="token operator">:</span> aload_2 <span class="token comment" spellcheck="true">// slot 2(lock引用)</span><span class="token number">27</span><span class="token operator">:</span> monitorexit <span class="token comment" spellcheck="true">// 【将 lock对象 MarkWord 重置, 唤醒 EntryList】</span><span class="token number">28</span><span class="token operator">:</span> aload_3<span class="token number">29</span><span class="token operator">:</span> athrow<span class="token number">30</span><span class="token operator">:</span> <span class="token keyword">return</span>Exception table<span class="token operator">:</span>    from to target type      <span class="token number">12</span> <span class="token number">22</span> <span class="token number">25</span> any      <span class="token number">25</span> <span class="token number">28</span> <span class="token number">25</span> anyLineNumberTable<span class="token operator">:</span> <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>LocalVariableTable<span class="token operator">:</span>    Start Length Slot Name Signature        <span class="token number">0</span> <span class="token number">31</span> <span class="token number">0</span> args <span class="token punctuation">[</span>Ljava<span class="token operator">/</span>lang<span class="token operator">/</span>String<span class="token punctuation">;</span>        <span class="token number">8</span> <span class="token number">23</span> <span class="token number">1</span> lock Ljava<span class="token operator">/</span>lang<span class="token operator">/</span>Object<span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>说明：</p><ul><li>通过异常 <strong>try-catch 机制</strong>，确保一定会被解锁</li><li>方法级别的 synchronized 不会在字节码指令中有所体现</li></ul><h3 id="锁升级"><a href="#锁升级" class="headerlink" title="锁升级"></a>锁升级</h3><h4 id="升级过程"><a href="#升级过程" class="headerlink" title="升级过程"></a>升级过程</h4><p><strong>synchronized 是可重入、不公平的重量级锁</strong>，所以可以对其进行优化</p><pre class="line-numbers language-java"><code class="language-java">无锁 <span class="token operator">-</span><span class="token operator">></span> 偏向锁 <span class="token operator">-</span><span class="token operator">></span> 轻量级锁 <span class="token operator">-</span><span class="token operator">></span> 重量级锁<span class="token comment" spellcheck="true">// 随着竞争的增加，只能锁升级，不能降级</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p><img src="https://img.jwt1399.top/img/202212271733295.png"></p><p><img src="https://img.jwt1399.top/img/202212301531550.png"></p><h4 id="偏向锁"><a href="#偏向锁" class="headerlink" title="偏向锁"></a>偏向锁</h4><p>偏向锁的思想是偏向于让第一个获取锁对象的线程，这个线程之后重新获取该锁不再需要同步操作：</p><ul><li>当锁对象第一次被线程获得的时候进入偏向状态，标记为 <code>101</code>，同时<strong>使用 CAS 操作将线程 ID 记录到 Mark Word</strong>。如果 CAS 操作成功，这个线程以后进入这个锁相关的同步块，查看这个线程 ID 是自己的就表示没有竞争，就不需要再进行任何同步操作</li><li>当有另外一个线程去尝试获取这个锁对象时，偏向状态就宣告结束，此时撤销偏向（Revoke Bias）后恢复到未锁定或轻量级锁状态</li></ul><p><img src="/../images/Java-JUC/image-20221227173941506.png"></p><pre class="line-numbers language-ruby"><code class="language-ruby">biased_lock<span class="token punctuation">:</span> 偏向锁是否开启 默认开启，即为<span class="token number">1</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>一个对象创建时：</p><ul><li>如果没有开启偏向锁，那么对象创建后，markword 值为 0x01 即最后 3 位为 001，这时它的 hashcode、age 都为 0，第一次用到 hashcode 时才会赋值</li></ul><ul><li>如果开启了偏向锁（默认开启），那么对象创建后，MarkWord 值为 0x05 即最后 3 位为 101，thread、epoch、age 都为 0</li><li>偏向锁是默认是延迟的，不会在程序启动时立即生效，如果想避免延迟，可以加 VM 参数 <code>-XX:BiasedLockingStartupDelay=0</code> 来禁用延迟。JDK 8 延迟 4s 开启偏向锁原因：在刚开始执行代码时，会有好多线程来抢锁，如果开偏向锁效率反而降低</li><li>添加 VM 参数 <code>-XX:-UseBiasedLocking</code> 禁用偏向锁</li></ul><p><strong>偏向锁</strong>只在第一次使用 CAS 将线程 ID 设置到对象的 Mark Word 头，之后发现这个线程 ID 是自己的就表示没有竞争，不用重新 CAS。以后只要不发生竞争，这个对象就归该线程所有。<strong>轻量级锁</strong>在没有竞争时（就自己这个线程），每次重入仍然需要执行 CAS 操作。</p><p><strong>撤销偏向锁的状态</strong>：</p><ul><li>调用对象的 hashCode：偏向锁的对象 MarkWord 中存储的是线程 id，调用 hashCode 导致偏向锁被撤销</li><li>当有其它线程使用偏向锁对象时，会将偏向锁升级为轻量级锁</li><li>调用 wait&#x2F;notify，需要申请 Monitor，进入 WaitSet</li></ul><p><strong>批量撤销</strong>：如果对象被多个线程访问，但没有竞争，这时偏向了线程 T1 的对象仍有机会重新偏向 T2，重偏向会重置对象的 Thread ID</p><ul><li><p>批量重偏向：当撤销偏向锁阈值超过 20 次后，JVM 会觉得是不是偏向错了，于是在给这些对象加锁时重新偏向至加锁线程</p></li><li><p>批量撤销：当撤销偏向锁阈值超过 40 次后，JVM 会觉得自己确实偏向错了，根本就不该偏向，于是整个类的所有对象都会变为不可偏向的，新建的对象也是不可偏向的</p></li></ul><h4 id="轻量级锁"><a href="#轻量级锁" class="headerlink" title="轻量级锁"></a>轻量级锁</h4><p>一个对象有多个线程要加锁，但加锁的时间是错开的（没有竞争），可以使用轻量级锁来优化，轻量级锁对使用者是透明的（不可见）</p><p>可重入锁：线程可以进入任何一个它已经拥有的锁所同步着的代码块，可重入锁最大的作用是<strong>避免死锁</strong></p><p>轻量级锁在没有竞争时（锁重入时），每次重入仍然需要执行 CAS 操作，Java 6 才引入的偏向锁来优化</p><p>CAS机制：Compare And Swap，表示比较并交换</p><p>锁重入实例：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">static</span> <span class="token keyword">final</span> Object obj <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Object</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">method1</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">synchronized</span><span class="token punctuation">(</span> obj <span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 同步块 A</span>        <span class="token function">method2</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">method2</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">synchronized</span><span class="token punctuation">(</span> obj <span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 同步块 B</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>1.创建锁记录（Lock Record）对象，每个线程的<strong>栈帧</strong>都会包含一个锁记录的结构，存储锁定对象的 <code>Mark Word</code>（Hashcode，Age，Bias 01（无锁））</p><p><img src="https://img.jwt1399.top/img/202212271752214.png"></p><p>2.让锁记录中 Object reference 指向锁住的对象，并尝试用 CAS 替换 Object 的 Mark Word（loack record 地址 00（轻量级锁）），将 Mark Word 的值存入锁记录</p><p>如果 CAS 替换成功，对象头中存储了锁记录地址和状态 00（轻量级锁） ，表示由该线程给对象加锁</p><p><img src="https://img.jwt1399.top/img/202212271856546.png"></p><p>如果 CAS 失败，有两种情况：</p><ul><li>如果是其它线程已经持有了该 Object 的轻量级锁，这时表明有竞争，进入<strong>锁膨胀过程</strong></li><li>如果是线程自己执行了 synchronized 锁重入，就再添加一条 Lock Record 作为重入的计数，存储null</li></ul><p><img src="https://img.jwt1399.top/img/202212271858706.png"></p><p>3.当退出 synchronized 代码块（解锁时）</p><ul><li>如果有取值为 null 的锁记录，表示有重入，这时重置锁记录，表示重入计数减 1</li><li>如果锁记录的值不为 null，这时使用 CAS <strong>将 Mark Word 的值恢复给对象头</strong><ul><li>成功，则解锁成功</li><li>失败，说明轻量级锁进行了锁膨胀或已经升级为重量级锁，进入重量级锁解锁流程</li></ul></li></ul><h4 id="锁膨胀-x2F-重量级锁"><a href="#锁膨胀-x2F-重量级锁" class="headerlink" title="锁膨胀&#x2F;重量级锁"></a>锁膨胀&#x2F;重量级锁</h4><p>在尝试加轻量级锁的过程中，CAS 操作无法成功，可能是其它线程为此对象加上了轻量级锁（有竞争），这时需要进行锁膨胀，将轻量级锁变为<strong>重量级锁</strong></p><ul><li>当 Thread-1 进行轻量级加锁时，Thread-0 已经对该对象加了轻量级锁</li></ul><p><img src="https://img.jwt1399.top/img/202212271903673.png"></p><ul><li>Thread-1 加轻量级锁失败，进入锁膨胀流程：为 Object 对象申请 Monitor 锁，<strong>通过 Object 对象头获取到持锁线程</strong>，将 Monitor 的 Owner 置为 Thread-0，将 Object 的对象头指向重量级锁地址，然后自己进入 Monitor 的 EntryList BLOCKED</li></ul><p><img src="https://img.jwt1399.top/img/202212271903923.png"></p><ul><li>当 Thread-0 退出同步块解锁时，使用 CAS 将 Mark Word 的值恢复给对象头失败，这时进入重量级解锁流程，即按照 Monitor 地址找到 Monitor 对象，设置 Owner 为 null，唤醒 EntryList 中 BLOCKED 线程</li></ul><h3 id="锁优化"><a href="#锁优化" class="headerlink" title="锁优化"></a>锁优化</h3><h4 id="自旋锁"><a href="#自旋锁" class="headerlink" title="自旋锁"></a>自旋锁</h4><p>重量级锁竞争时，尝试获取锁的线程不会立即阻塞，可以使用<strong>自旋</strong>（默认 10 次）来进行优化，采用循环的方式去尝试获取锁</p><ul><li>自旋占用 CPU 时间，单核 CPU 自旋就是浪费时间，因为同一时刻只能运行一个线程，多核 CPU 自旋才能发挥优势</li><li>自旋失败的线程会进入阻塞状态</li></ul><p>优点：不会进入阻塞状态，<strong>减少线程上下文切换的消耗</strong></p><p>缺点：当自旋的线程越来越多时，会不断的消耗 CPU 资源</p><p>自旋锁情况：</p><table><thead><tr><th>自旋成功的情况</th><th>自旋失败的情况</th></tr></thead><tbody><tr><td><img src="https://img.jwt1399.top/img/202212271918731.png" style="zoom: 80%;" /></td><td><img src="https://img.jwt1399.top/img/202212271918251.png" style="zoom:80%;" /></td></tr></tbody></table><p>自旋锁说明：</p><ul><li>在 Java 6 之后自旋锁是自适应的，比如对象刚刚的一次自旋操作成功过，那么认为这次自旋成功的可能性会高，就多自旋几次；反之，就少自旋甚至不自旋，比较智能</li><li>Java 7 之后不能控制是否开启自旋功能，由 JVM 控制</li></ul><h4 id="锁消除"><a href="#锁消除" class="headerlink" title="锁消除"></a>锁消除</h4><p>锁消除是指对于被检测出不可能存在竞争的共享数据的锁进行消除，这是 JVM <strong>即时编译器的优化</strong></p><p>锁消除主要是通过<strong>逃逸分析</strong>来支持，如果堆上的共享数据不可能逃逸出去被其它线程访问到，那么就可以把它们当成私有数据对待，也就可以将它们的锁进行消除（同步消除：JVM 逃逸分析）</p><p><strong>同步消除 (Synchronization Elimination)<strong>：线程同步本身比较耗时，如果确定一个对象不会逃逸出线程，不被其它线程访问到，那对象的读写就不会存在竞争，则可以消除对该对象的</strong>同步锁</strong>，通过 <code>-XX:+EliminateLocks</code> 可以开启同步消除 ( - 号关闭)</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">MyBenchmark</span> <span class="token punctuation">{</span>    <span class="token keyword">static</span> <span class="token keyword">int</span> x <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">a</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">throws</span> Exception <span class="token punctuation">{</span>        x<span class="token operator">++</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>      <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">b</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">throws</span> Exception <span class="token punctuation">{</span>      Object o <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Object</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token keyword">synchronized</span> <span class="token punctuation">(</span>o<span class="token punctuation">)</span> <span class="token punctuation">{</span>          x<span class="token operator">++</span><span class="token punctuation">;</span>      <span class="token punctuation">}</span>      <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>a方法和b方法执行效率差不多，应为b方法中o对象不会逃逸出线程，不被其它线程访问到，那o对象的读写就不会存在竞争，则可以消除对o对象的<strong>同步锁</strong>，效果就跟a方法一样</p><h4 id="锁粗化"><a href="#锁粗化" class="headerlink" title="锁粗化"></a>锁粗化</h4><p>对相同对象多次加锁，导致线程发生多次重入，频繁的加锁操作就会导致性能损耗，可以使用锁粗化方式优化</p><p>如果虚拟机探测到一串的操作都对同一个对象加锁，将会把加锁的范围扩展（粗化）到整个操作序列的外部</p><ul><li><p>一些看起来没有加锁的代码，其实隐式的加了很多锁：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">static</span> String <span class="token function">concatString</span><span class="token punctuation">(</span>String s1<span class="token punctuation">,</span> String s2<span class="token punctuation">,</span> String s3<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">return</span> s1 <span class="token operator">+</span> s2 <span class="token operator">+</span> s3<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre></li><li><p>String 是一个不可变的类，编译器会对 String 的拼接自动优化。在 JDK 1.5 之前，转化为 StringBuffer 对象的连续 append() 操作，每个 append() 方法中都有一个同步块</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">static</span> String <span class="token function">concatString</span><span class="token punctuation">(</span>String s1<span class="token punctuation">,</span> String s2<span class="token punctuation">,</span> String s3<span class="token punctuation">)</span> <span class="token punctuation">{</span>    StringBuffer sb <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">StringBuffer</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    sb<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span>s1<span class="token punctuation">)</span><span class="token punctuation">;</span>    sb<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span>s2<span class="token punctuation">)</span><span class="token punctuation">;</span>    sb<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span>s3<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> sb<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li></ul><p>扩展到第一个 append() 操作之前直至最后一个 append() 操作之后，只需要加锁一次就可以</p><h3 id="多把锁"><a href="#多把锁" class="headerlink" title="多把锁"></a>多把锁</h3><p>多把不相干的锁：一间大屋子有两个功能睡觉、学习，互不相干。现在一人要学习，一人要睡觉，如果只用一间屋子（一个对象锁）的话，那么并发度很低</p><p>将锁的粒度细分：</p><ul><li>好处，是可以增强并发度</li><li>坏处，如果一个线程需要同时获得多把锁，就容易发生死锁</li></ul><p>解决方法：准备多个对象锁</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>    BigRoom bigRoom <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">BigRoom</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span> bigRoom<span class="token punctuation">.</span><span class="token function">study</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span> bigRoom<span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token keyword">class</span> <span class="token class-name">BigRoom</span> <span class="token punctuation">{</span>    <span class="token keyword">private</span> <span class="token keyword">final</span> Object studyRoom <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Object</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">private</span> <span class="token keyword">final</span> Object sleepRoom <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Object</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">sleep</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">throws</span> InterruptedException <span class="token punctuation">{</span>        <span class="token keyword">synchronized</span> <span class="token punctuation">(</span>sleepRoom<span class="token punctuation">)</span> <span class="token punctuation">{</span>            System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"sleeping 2 小时"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            Thread<span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">2000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">study</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">throws</span> InterruptedException <span class="token punctuation">{</span>        <span class="token keyword">synchronized</span> <span class="token punctuation">(</span>studyRoom<span class="token punctuation">)</span> <span class="token punctuation">{</span>            System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"study 1 小时"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            Thread<span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">1000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="死锁"><a href="#死锁" class="headerlink" title="死锁"></a>死锁</h3><h4 id="死锁形成"><a href="#死锁形成" class="headerlink" title="死锁形成"></a>死锁形成</h4><p>死锁：多个线程同时被阻塞，它们中的一个或者全部都在等待某个资源被释放，由于线程被无限期地阻塞，因此程序不可能正常终止</p><p>Java 死锁产生的四个必要条件：</p><ol><li>互斥条件，即当资源被一个线程使用（占有）时，别的线程不能使用</li><li>不可剥夺条件，资源请求者不能强制从资源占有者手中夺取资源，资源只能由资源占有者主动释放</li><li>请求和保持条件，即当资源请求者在请求其他的资源的同时保持对原有资源的占有</li><li>循环等待条件，即存在一个等待循环队列：p1 要 p2 的资源，p2 要 p1 的资源，形成了一个等待环路</li></ol><p>四个条件都成立的时候，便形成死锁。死锁情况下打破上述任何一个条件，便可让死锁消失</p><p>死锁代码：</p><p>一个线程需要同时获取多把锁，这时就容易发生死锁</p><ul><li><p>t1 线程 获得 A对象 锁，接下来想获取 B对象的锁</p></li><li><p>t2 线程 获得 B对象 锁，接下来想获取 A对象的锁</p></li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Dead</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> Object resources1 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Object</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> Object resources2 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Object</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span>  <span class="token punctuation">{</span>        <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 线程1：占用资源1 ，请求资源2</span>            <span class="token keyword">synchronized</span><span class="token punctuation">(</span>resources1<span class="token punctuation">)</span> <span class="token punctuation">{</span>                System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"线程1已经占用了资源1，开始请求资源2"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token keyword">try</span> <span class="token punctuation">{</span>                    <span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">2000</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//保证线程2先获得资源2</span>                <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">InterruptedException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span>                    <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">RuntimeException</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token punctuation">}</span>                <span class="token comment" spellcheck="true">//2秒内线程2肯定可以锁住资源2</span>                <span class="token keyword">synchronized</span> <span class="token punctuation">(</span>resources2<span class="token punctuation">)</span> <span class="token punctuation">{</span>                    System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"线程1已经占用了资源2"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token punctuation">}</span>            <span class="token punctuation">}</span>            <span class="token punctuation">}</span><span class="token punctuation">,</span><span class="token string">"t1"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span>                <span class="token comment" spellcheck="true">// 线程2：占用资源2 ，请求资源1</span>                <span class="token keyword">synchronized</span><span class="token punctuation">(</span>resources2<span class="token punctuation">)</span><span class="token punctuation">{</span>                    System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"线程2已经占用了资源2，开始请求资源1"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                    <span class="token keyword">try</span> <span class="token punctuation">{</span>                        <span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">2000</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//保证线程1先获得资源1</span>                    <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">InterruptedException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span>                        <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">RuntimeException</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span><span class="token punctuation">;</span>                    <span class="token punctuation">}</span>                    <span class="token keyword">synchronized</span> <span class="token punctuation">(</span>resources1<span class="token punctuation">)</span><span class="token punctuation">{</span>                        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"线程2已经占用了资源1"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                    <span class="token punctuation">}</span>                <span class="token punctuation">}</span>        <span class="token punctuation">}</span><span class="token punctuation">,</span><span class="token string">"t2"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="死锁定位"><a href="#死锁定位" class="headerlink" title="死锁定位"></a>死锁定位</h4><p>定位死锁的方法：</p><ul><li><p>使用 jps 定位进程 id，再用 <code>jstack id</code> 定位死锁，找到死锁的线程去查看源码，解决优化</p><pre class="line-numbers language-sh"><code class="language-sh">Found one Java-level deadlock:============================="t2":  waiting to lock monitor 0x0000000126019ac0 (object 0x000000076ac25d88, a java.lang.Object),  which is held by "t1""t1":  waiting to lock monitor 0x000000012601c400 (object 0x000000076ac25d98, a java.lang.Object),  which is held by "t2"Java stack information for the threads listed above:==================================================="t2":        at JJTest.Dead.lambda$main$1(Dead.java:43)        - waiting to lock <0x000000076ac25d88> (a java.lang.Object)        - locked <0x000000076ac25d98> (a java.lang.Object)        at JJTest.Dead$$Lambda$2/1096979270.run(Unknown Source)        at java.lang.Thread.run(Thread.java:748)"t1":        at JJTest.Dead.lambda$main$0(Dead.java:28)        - waiting to lock <0x000000076ac25d98> (a java.lang.Object)        - locked <0x000000076ac25d88> (a java.lang.Object)        at JJTest.Dead$$Lambda$1/2003749087.run(Unknown Source)        at java.lang.Thread.run(Thread.java:748)Found 1 deadlock.<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>Linux 下可以通过 top 先定位到 CPU 占用高的 Java 进程，再利用 <code>top -Hp 进程id</code> 来定位是哪个线程，最后再用 <code>jstack &lt;pid&gt;</code>的输出来看各个线程栈</p></li><li><p>可以使用可视化工具 jconsole 、Visual VM</p></li><li><p>避免死锁：避免死锁要注意加锁顺序</p></li></ul><h4 id="解决死锁"><a href="#解决死锁" class="headerlink" title="解决死锁"></a>解决死锁</h4><p>解决该问题最简单的方式就是两个线程按顺序获取资源，线程1和线程2都先获取资源1再获取资源2，无论哪个线程先获取到资源1，另一个线程都会因无法获取线程1产生阻塞，等到先获取到资源1的线程释放资源1，另一个线程获取资源1，这样两个线程可以轮流获取资源1和资源2。</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Dead</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> Object resources1 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Object</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> Object resources2 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Object</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span>  <span class="token punctuation">{</span>        <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 线程1：先请求资源1 ，再请求资源2</span>            <span class="token keyword">synchronized</span><span class="token punctuation">(</span>resources1<span class="token punctuation">)</span> <span class="token punctuation">{</span>                System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"线程1已经占用了资源1，开始请求资源2"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token keyword">try</span> <span class="token punctuation">{</span>                    <span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">2000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">InterruptedException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span>                    <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">RuntimeException</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token punctuation">}</span>                <span class="token keyword">synchronized</span> <span class="token punctuation">(</span>resources2<span class="token punctuation">)</span> <span class="token punctuation">{</span>                    System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"线程1已经占用了资源2"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token punctuation">}</span>            <span class="token punctuation">}</span>            <span class="token punctuation">}</span><span class="token punctuation">,</span><span class="token string">"t1"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span>                <span class="token comment" spellcheck="true">// 线程2：先请求资源1 ，再请求资源2</span>                <span class="token keyword">synchronized</span><span class="token punctuation">(</span>resources1<span class="token punctuation">)</span><span class="token punctuation">{</span>                    System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"线程2已经占用了资源2，开始请求资源1"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                    <span class="token keyword">try</span> <span class="token punctuation">{</span>                        <span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">2000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                    <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">InterruptedException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span>                        <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">RuntimeException</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span><span class="token punctuation">;</span>                    <span class="token punctuation">}</span>                    <span class="token keyword">synchronized</span> <span class="token punctuation">(</span>resources2<span class="token punctuation">)</span><span class="token punctuation">{</span>                        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"线程2已经占用了资源1"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                    <span class="token punctuation">}</span>                <span class="token punctuation">}</span>        <span class="token punctuation">}</span><span class="token punctuation">,</span><span class="token string">"t2"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="活锁"><a href="#活锁" class="headerlink" title="活锁"></a>活锁</h3><p>活锁：指的是任务或者执行者没有被阻塞，由于某些条件没有满足，导致一直重复尝试—失败—尝试—失败的过程</p><p>两个线程互相改变对方的结束条件，最后谁也无法结束：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">TestLiveLock</span> <span class="token punctuation">{</span>    <span class="token keyword">static</span> <span class="token keyword">volatile</span> <span class="token keyword">int</span> count <span class="token operator">=</span> <span class="token number">10</span><span class="token punctuation">;</span>    <span class="token keyword">static</span> <span class="token keyword">final</span> Object lock <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Object</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 期望减到 0 退出循环</span>            <span class="token keyword">while</span> <span class="token punctuation">(</span>count <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                Thread<span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">200</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                count<span class="token operator">--</span><span class="token punctuation">;</span>                System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"线程一count:"</span> <span class="token operator">+</span> count<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token string">"t1"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 期望超过 20 退出循环</span>            <span class="token keyword">while</span> <span class="token punctuation">(</span>count <span class="token operator">&lt;</span> <span class="token number">20</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                Thread<span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">200</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                count<span class="token operator">++</span><span class="token punctuation">;</span>                System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"线程二count:"</span><span class="token operator">+</span> count<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token string">"t2"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="饥饿"><a href="#饥饿" class="headerlink" title="饥饿"></a>饥饿</h3><p>饥饿：一个或者多个线程因为种种原因无法获得所需要的资源， 导致一直无法执行的状态。</p><p>以打印机打印文件为例，当有多个线程需要打印文件，系统按照短文件优先的策略进行打印，但当短文件的打印任务一直不间断地出现，那长文件的打印任务会被一直推迟，导致饥饿。活锁就是在忙式等待条件下发生的饥饿，忙式等待就是不进入等待状态的等待。</p><p>产生饥饿的原因：</p><ul><li>高优先级的线程占用了低优先级线程的CPU时间</li><li>线程被永久堵塞在一个等待进入同步块的状态，因为其他线程总是能在它之前持续地对该同步块进行访问。</li><li>线程在等待一个本身也处于永久等待完成的对象(比如调用这个对象的<code>wait()</code>方法)，因为其他线程总是被持续地获得唤醒。</li></ul><p>死锁、饥饿的区别：饥饿可自行解开，死锁不行。</p><h2 id="❹wait-notify"><a href="#❹wait-notify" class="headerlink" title="❹wait notify"></a>❹wait notify</h2><h3 id="基本使用"><a href="#基本使用" class="headerlink" title="基本使用"></a>基本使用</h3><p>Object 类 API：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token keyword">void</span> <span class="token function">wait</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">:</span>导致当前线程等待，直到另一个线程调用该对象的<span class="token function">notify</span><span class="token punctuation">(</span><span class="token punctuation">)</span>方法或 <span class="token function">notifyAll</span><span class="token punctuation">(</span><span class="token punctuation">)</span>方法。<span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token keyword">native</span> <span class="token keyword">void</span> <span class="token function">wait</span><span class="token punctuation">(</span><span class="token keyword">long</span> timeout<span class="token punctuation">)</span><span class="token operator">:</span>有时限的等待<span class="token punctuation">,</span> 到n毫秒后结束等待，或是被唤醒<span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token keyword">void</span> <span class="token function">notify</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">:</span>唤醒正在等待对象监视器的单个线程。<span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token keyword">void</span> <span class="token function">notifyAll</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">:</span>唤醒正在等待对象监视器的所有线程。<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p><strong>wait 是挂起线程，需要唤醒的都是挂起操作</strong>，阻塞线程可以自己去争抢锁，挂起的线程需要唤醒后去争抢锁</p><p>对比 sleep()：</p><ul><li>原理不同：sleep() 方法是属于 Thread 类，是线程用来控制自身流程的，使此线程暂停执行一段时间而把执行机会让给其他线程；wait() 方法属于 Object 类，用于线程间通信</li><li>对<strong>锁的处理机制</strong>不同：调用 sleep() 方法的过程中，线程不会释放对象锁，当调用 wait() 方法的时候，线程会放弃对象锁，进入等待此对象的等待锁定池，但是都会释放 CPU</li><li>使用区域不同：wait() 方法必须放在<strong>同步控制方法和同步代码块（即必须先获取锁）</strong>中使用，sleep() 方法则可以放在任何地方使用</li></ul><p>底层原理：</p><ul><li>Owner 发现该线程条件不满足，调用 wait 方法，即可进入 WaitSet 变为 WAITING 状态</li><li>BLOCKED 和 WAITING 的线程都处于阻塞状态，不占用 CPU 时间片</li><li>BLOCKED 线程会在 Owner 线程释放锁时唤醒</li><li>WAITING 线程会在 Owner 线程调用 notify 或 notifyAll 时唤醒，唤醒后并不意味者立刻获得锁，<strong>需要进入 EntryList 重新竞争</strong></li></ul><p><img src="https://img.jwt1399.top/img/202212271941505.png"></p><h3 id="代码优化"><a href="#代码优化" class="headerlink" title="代码优化"></a>代码优化</h3><p>虚假唤醒：notify 只能随机唤醒一个 WaitSet 中线程，这时如果有其它线程也在等待，那么就可能唤醒不了正确的线程</p><p>解决方法：采用 notifyAll</p><p>notifyAll 仅解决某个线程的唤醒问题，使用 if + wait 判断仅有一次机会，一旦条件不成立，无法重新判断</p><p>解决方法：用 while + wait，当条件不成立，再次 wait</p><p>wait notify正确使用代码模版：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">//挂起线程</span><span class="token keyword">synchronized</span><span class="token punctuation">(</span>lock<span class="token punctuation">)</span><span class="token punctuation">{</span>      <span class="token keyword">while</span><span class="token punctuation">(</span>条件成立<span class="token punctuation">)</span><span class="token punctuation">{</span>          lock<span class="token punctuation">.</span><span class="token function">wait</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>      <span class="token comment" spellcheck="true">// 工作代码</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">//唤醒线程</span><span class="token keyword">synchronized</span><span class="token punctuation">(</span>lock<span class="token punctuation">)</span><span class="token punctuation">{</span>  lock<span class="token punctuation">.</span><span class="token function">notifyAll</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="❺park-unpark"><a href="#❺park-unpark" class="headerlink" title="❺park unpark"></a>❺park unpark</h2><h3 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h3><p><code>park</code> 和 <code>unpark</code> 是 LockSupport 类中的方法，LockSupport 是用来创建锁和其他同步类的<strong>线程原语</strong></p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// 暂停当前线程，挂起原语</span>LockSupport<span class="token punctuation">.</span><span class="token function">park</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//暂停当前线程，并指定等待时间</span>LockSupport<span class="token punctuation">.</span><span class="token function">parkNanos</span><span class="token punctuation">(</span>Object blocker<span class="token punctuation">,</span> <span class="token keyword">long</span> nanos<span class="token punctuation">)</span><span class="token comment" spellcheck="true">//暂停当前线程，并指定截止时间</span>LockSupport<span class="token punctuation">.</span><span class="token function">parkUntil</span><span class="token punctuation">(</span>Object blocker<span class="token punctuation">,</span> <span class="token keyword">long</span> deadline<span class="token punctuation">)</span>  <span class="token comment" spellcheck="true">// 恢复某个线程的运行</span>LockSupport<span class="token punctuation">.</span><span class="token function">unpark</span><span class="token punctuation">(</span>暂停线程对象<span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>结论：先 park 再 unpark 和先 unpark 再 park 效果一样，都会直接恢复线程的运行</strong></p><ul><li>先 park 再 unpark</li></ul><pre class="line-numbers language-java"><code class="language-java">Thread t1 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span>    log<span class="token punctuation">.</span><span class="token function">debug</span><span class="token punctuation">(</span><span class="token string">"start..."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    log<span class="token punctuation">.</span><span class="token function">debug</span><span class="token punctuation">(</span><span class="token string">"park..."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    LockSupport<span class="token punctuation">.</span><span class="token function">park</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    log<span class="token punctuation">.</span><span class="token function">debug</span><span class="token punctuation">(</span><span class="token string">"resume..."</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token punctuation">,</span><span class="token string">"t1"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>t1<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span>log<span class="token punctuation">.</span><span class="token function">debug</span><span class="token punctuation">(</span><span class="token string">"unpark..."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>LockSupport<span class="token punctuation">.</span><span class="token function">unpark</span><span class="token punctuation">(</span>t1<span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre><code>18:42:52.585 c.TestParkUnpark [t1] - start...18:42:53.589 c.TestParkUnpark [t1] - park...18:42:54.583 c.TestParkUnpark [main] - unpark...18:42:54.583 c.TestParkUnpark [t1] - resume...</code></pre><ul><li>先 unpark 再 park</li></ul><pre class="line-numbers language-java"><code class="language-java">Thread t1 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span>    log<span class="token punctuation">.</span><span class="token function">debug</span><span class="token punctuation">(</span><span class="token string">"start..."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    log<span class="token punctuation">.</span><span class="token function">debug</span><span class="token punctuation">(</span><span class="token string">"park..."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    LockSupport<span class="token punctuation">.</span><span class="token function">park</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    log<span class="token punctuation">.</span><span class="token function">debug</span><span class="token punctuation">(</span><span class="token string">"resume..."</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token string">"t1"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>t1<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>log<span class="token punctuation">.</span><span class="token function">debug</span><span class="token punctuation">(</span><span class="token string">"unpark..."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>LockSupport<span class="token punctuation">.</span><span class="token function">unpark</span><span class="token punctuation">(</span>t1<span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre><code>18:43:50.765 c.TestParkUnpark [t1] - start...18:43:51.764 c.TestParkUnpark [main] - unpark...18:43:52.769 c.TestParkUnpark [t1] - park...18:43:52.769 c.TestParkUnpark [t1] - resume...</code></pre><p>与 Object 类 的 wait &amp; notify 相比</p><ul><li>wait，notify 和 notifyAll 必须配合 Object Monitor 一起使用，而 park、unpark 不需要</li><li>park ，unpark <strong>以线程为单位</strong>来阻塞和唤醒线程，而 notify 只能随机唤醒一个等待线程，notifyAll 是唤醒所有等待线程</li><li>park ，unpark 可以先 unpark，而 wait &amp; notify 不能先 notify。</li><li>wait 会释放锁资源进入等待队列，<strong>park 不会释放锁资源</strong>，只负责阻塞当前线程，会释放 CPU</li></ul><h3 id="原理"><a href="#原理" class="headerlink" title="原理"></a>原理</h3><p>每个线程都有自己的一个 <code>Parker</code> 对象，由三部分组成 <code>_counter</code> ， <code>_cond</code> 和 <code>_mutex</code> </p><p>打个比喻<code>线程</code>就像一个旅人，<code>Parker</code> 就像他随身携带的背包，<code>_cond</code> 就好比背包中的帐篷，<code>_counter</code> 就好比背包中的备用干粮（0 为耗尽，1 为充足）</p><ul><li><p>调用 <code>park</code> 就是要看需不需要停下来歇息。</p><ul><li>如果备用干粮耗尽，那么钻进帐篷歇息</li><li>如果备用干粮充足，那么不需停留，继续前进</li></ul></li><li><p>调用 <code>unpark</code>，就好比令干粮充足</p><ul><li><p>如果这时线程还在帐篷，就唤醒让他继续前进</p></li><li><p>如果这时线程还在运行，那么下次他调用 park 时，仅是消耗掉备用干粮，不需停留继续前进</p></li><li><p>因为背包空间有限，多次调用 unpark 仅会补充一份备用干粮</p></li></ul></li></ul><p><strong>先 park 再 unpark</strong></p><ol><li>当前线程调用 <code>Unsafe.park()</code> 方法</li><li>检查 <code>_counter</code> ，本情况为 0，这时获得 <code>_mutex</code> 互斥锁</li><li>线程进入 <code>_cond</code> 条件变量挂起</li><li>调用 <code>Unsafe.unpark(Thread_0)</code> 方法，设置 <code>_counter</code> 为 1</li><li>唤醒 <code>_cond</code> 条件变量中的 <code>Thread_0</code>，<code>Thread_0</code> 恢复运行，设置 <code>_counter</code> 为 0</li></ol><table><thead><tr><th align="center">park</th><th align="center">unpark</th></tr></thead><tbody><tr><td align="center"><img src="https://img.jwt1399.top/img/202212272238066.png"></td><td align="center"><img src="https://img.jwt1399.top/img/202212272238925.png"></td></tr></tbody></table><p><strong>先 unpark 再 park</strong></p><ol><li>调用 <code>Unsafe.unpark(Thread_0)</code> 方法，设置 <code>_counter</code> 为 1</li><li>当前线程调用 <code>Unsafe.park()</code> 方法</li><li>检查 <code>_counter</code> ，本情况为 1，这时线程无需挂起，继续运行，设置 <code>_counter</code> 为 0</li></ol><p><img src="https://img.jwt1399.top/img/202212272238449.png"></p><h2 id="❻ReentrantLock"><a href="#❻ReentrantLock" class="headerlink" title="❻ReentrantLock"></a>❻ReentrantLock</h2><h3 id="锁对比"><a href="#锁对比" class="headerlink" title="锁对比"></a>锁对比</h3><p>ReentrantLock 相对于 synchronized 具备如下特点：</p><ol><li>锁的实现：synchronized 是 JVM 实现的，而 ReentrantLock 是 JDK 实现的</li><li>性能：新版本 Java 对 synchronized 进行了很多优化，synchronized 与 ReentrantLock 大致相同</li><li>使用：ReentrantLock 需要手动解锁，synchronized 执行完代码块自动解锁</li><li><strong>可中断</strong>：ReentrantLock 可中断，而 synchronized 不行</li><li><strong>公平锁</strong>：公平锁是指多个线程在等待同一个锁时，必须按照申请锁的时间顺序来依次获得锁<ul><li>ReentrantLock 可以设置公平锁，synchronized 中的锁是非公平的</li><li>不公平锁的含义是阻塞队列内公平，队列外非公平</li></ul></li><li>锁超时：尝试获取锁，超时获取不到直接放弃，不进入阻塞队列<ul><li>ReentrantLock 可以设置超时时间，synchronized 会一直等待</li></ul></li><li>锁绑定多个条件：一个 ReentrantLock 可以同时绑定多个 Condition 对象，更细粒度的唤醒线程</li><li>两者都是可重入锁</li></ol><h3 id="使用锁"><a href="#使用锁" class="headerlink" title="使用锁"></a>使用锁</h3><p>构造方法：<code>ReentrantLock lock = new ReentrantLock();</code></p><p>ReentrantLock 类 API：</p><ul><li><p><code>public void lock()</code>：获得锁</p><ul><li><p>如果锁没有被另一个线程占用，则将锁定计数设置为 1</p></li><li><p>如果当前线程已经保持锁定，则保持计数增加 1 </p></li><li><p>如果锁被另一个线程保持，则当前线程被禁用线程调度，并且在锁定已被获取之前处于休眠状态</p></li></ul></li><li><p><code>public void unlock()</code>：尝试释放锁</p><ul><li>如果当前线程是该锁的持有者，则保持计数递减</li><li>如果保持计数现在为零，则锁定被释放</li><li>如果当前线程不是该锁的持有者，则抛出异常</li></ul></li></ul><p>基本语法：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// 获取锁</span>reentrantLock<span class="token punctuation">.</span><span class="token function">lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">try</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 临界区</span><span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 释放锁</span>    reentrantLock<span class="token punctuation">.</span><span class="token function">unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="公平锁"><a href="#公平锁" class="headerlink" title="公平锁"></a>公平锁</h3><h4 id="基本使用-1"><a href="#基本使用-1" class="headerlink" title="基本使用"></a>基本使用</h4><p>构造方法：<code>ReentrantLock lock = new ReentrantLock(true)</code></p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token function">ReentrantLock</span><span class="token punctuation">(</span><span class="token keyword">boolean</span> fair<span class="token punctuation">)</span> <span class="token punctuation">{</span>    sync <span class="token operator">=</span> fair <span class="token operator">?</span> <span class="token keyword">new</span> <span class="token class-name">FairSync</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">:</span> <span class="token keyword">new</span> <span class="token class-name">NonfairSync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>ReentrantLock 默认是不公平的：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token function">ReentrantLock</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    sync <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">NonfairSync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>说明：公平锁一般没有必要，会降低并发度</p><h4 id="公平锁原理"><a href="#公平锁原理" class="headerlink" title="公平锁原理"></a>公平锁原理</h4><p>参考下方同步器章节中ReentrantLock部分</p><h4 id="非公平锁原理"><a href="#非公平锁原理" class="headerlink" title="非公平锁原理"></a>非公平锁原理</h4><p>参考下方同步器章节中ReentrantLock部分</p><h3 id="可重入"><a href="#可重入" class="headerlink" title="可重入"></a>可重入</h3><p>可重入是指同一个线程如果首次获得了这把锁，那么它是这把锁的拥有者，因此有权利再次获取这把锁，如果不可重入锁，那么第二次获得锁时，自己也会被锁挡住，直接造成死锁</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">static</span> ReentrantLock lock <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ReentrantLock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token function">method1</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">method1</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    lock<span class="token punctuation">.</span><span class="token function">lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">try</span> <span class="token punctuation">{</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">" execute method1"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token function">method2</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>        lock<span class="token punctuation">.</span><span class="token function">unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">method2</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    lock<span class="token punctuation">.</span><span class="token function">lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">try</span> <span class="token punctuation">{</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">" execute method2"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>        lock<span class="token punctuation">.</span><span class="token function">unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>源码解析参考：<code>nonfairTryAcquire(int acquires)) </code> 和 <code>tryRelease(int releases)</code></p><p><code>ReentrantLock</code>内部自定义了同步器sync，在加锁的时候通过CAS算法，将线程对象放到一个双向链表中，每次获取锁的时候，检查当前维护的那个线程ID和当前请求的线程ID是否 一致，如果一致，同步状态加1，表示锁被当前线程获取了多次。</p><p>源码如下：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">final</span> <span class="token keyword">boolean</span> <span class="token function">nonfairTryAcquire</span><span class="token punctuation">(</span><span class="token keyword">int</span> acquires<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">final</span> Thread current <span class="token operator">=</span> Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">int</span> c <span class="token operator">=</span> <span class="token function">getState</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>c <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">compareAndSetState</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> acquires<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token function">setExclusiveOwnerThread</span><span class="token punctuation">(</span>current<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span>    <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>current <span class="token operator">==</span> <span class="token function">getExclusiveOwnerThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">int</span> nextc <span class="token operator">=</span> c <span class="token operator">+</span> acquires<span class="token punctuation">;</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>nextc <span class="token operator">&lt;</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// overflow</span>            <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token string">"Maximum lock count exceeded"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token function">setState</span><span class="token punctuation">(</span>nextc<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>在 Lock 方法加两把锁会是什么情况呢？</p><ul><li>加锁两次解锁两次：正常执行</li><li>加锁两次解锁一次：程序直接卡死，线程不能出来，也就说明<strong>申请几把锁，最后需要解除几把锁</strong></li><li>加锁一次解锁两次：运行程序会直接报错</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">getLock</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    lock<span class="token punctuation">.</span><span class="token function">lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    lock<span class="token punctuation">.</span><span class="token function">lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">try</span> <span class="token punctuation">{</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">"\t get Lock"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>        lock<span class="token punctuation">.</span><span class="token function">unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">//lock.unlock();</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="可打断"><a href="#可打断" class="headerlink" title="可打断"></a>可打断</h3><h4 id="基本使用-2"><a href="#基本使用-2" class="headerlink" title="基本使用"></a>基本使用</h4><p><code>public void lockInterruptibly()</code>：获得可打断的锁</p><ul><li>如果没有竞争此方法就会获取 lock 对象锁</li><li>如果有竞争就进入阻塞队列，可以被其他线程用 interrupt 打断</li></ul><p>注意：如果是不可中断模式，那么即使使用了 interrupt 也不会让等待状态中的线程中断</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token keyword">throws</span> InterruptedException <span class="token punctuation">{</span>        ReentrantLock lock <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ReentrantLock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        Thread t1 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span>                <span class="token keyword">try</span> <span class="token punctuation">{</span>                        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"尝试获取锁"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                        lock<span class="token punctuation">.</span><span class="token function">lockInterruptibly</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">InterruptedException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span>                        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"没有获取到锁，被打断，直接返回"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                        <span class="token keyword">return</span><span class="token punctuation">;</span>                <span class="token punctuation">}</span>                <span class="token keyword">try</span> <span class="token punctuation">{</span>                        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"获取到锁"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>                        lock<span class="token punctuation">.</span><span class="token function">unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token punctuation">}</span>        <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token string">"t1"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        lock<span class="token punctuation">.</span><span class="token function">lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        t1<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        Thread<span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">2000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"主线程进行打断锁"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        t1<span class="token punctuation">.</span><span class="token function">interrupt</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="实现原理"><a href="#实现原理" class="headerlink" title="实现原理"></a>实现原理</h4><p>参考下方同步器章节中ReentrantLock部分</p><h3 id="锁超时"><a href="#锁超时" class="headerlink" title="锁超时"></a>锁超时</h3><h4 id="基本使用-3"><a href="#基本使用-3" class="headerlink" title="基本使用"></a>基本使用</h4><p><code>public boolean tryLock()</code>：尝试获取锁，获取到返回 true，获取不到直接放弃，不进入阻塞队列</p><p><code>public boolean tryLock(long timeout, TimeUnit unit)</code>：在给定时间内获取锁，获取不到就退出</p><p>注意：tryLock 期间也可以被打断</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>    ReentrantLock lock <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ReentrantLock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    Thread t1 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span>        <span class="token keyword">try</span> <span class="token punctuation">{</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>lock<span class="token punctuation">.</span><span class="token function">tryLock</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">,</span> TimeUnit<span class="token punctuation">.</span>SECONDS<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"获取不到锁"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token keyword">return</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">InterruptedException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span>            System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"被打断，获取不到锁"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">return</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">try</span> <span class="token punctuation">{</span>            log<span class="token punctuation">.</span><span class="token function">debug</span><span class="token punctuation">(</span><span class="token string">"获取到锁"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>            lock<span class="token punctuation">.</span><span class="token function">unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token string">"t1"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    lock<span class="token punctuation">.</span><span class="token function">lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"主线程获取到锁"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    t1<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        Thread<span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">1000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">try</span> <span class="token punctuation">{</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"主线程释放了锁"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>        lock<span class="token punctuation">.</span><span class="token function">unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="哲学家就餐"><a href="#哲学家就餐" class="headerlink" title="哲学家就餐"></a>哲学家就餐</h4><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>    Chopstick c1 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Chopstick</span><span class="token punctuation">(</span><span class="token string">"1"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//...</span>    Chopstick c5 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Chopstick</span><span class="token punctuation">(</span><span class="token string">"5"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">new</span> <span class="token class-name">Philosopher</span><span class="token punctuation">(</span><span class="token string">"苏格拉底"</span><span class="token punctuation">,</span> c1<span class="token punctuation">,</span> c2<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">new</span> <span class="token class-name">Philosopher</span><span class="token punctuation">(</span><span class="token string">"柏拉图"</span><span class="token punctuation">,</span> c2<span class="token punctuation">,</span> c3<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">new</span> <span class="token class-name">Philosopher</span><span class="token punctuation">(</span><span class="token string">"亚里士多德"</span><span class="token punctuation">,</span> c3<span class="token punctuation">,</span> c4<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">new</span> <span class="token class-name">Philosopher</span><span class="token punctuation">(</span><span class="token string">"赫拉克利特"</span><span class="token punctuation">,</span> c4<span class="token punctuation">,</span> c5<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">new</span> <span class="token class-name">Philosopher</span><span class="token punctuation">(</span><span class="token string">"阿基米德"</span><span class="token punctuation">,</span> c5<span class="token punctuation">,</span> c1<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token keyword">class</span> <span class="token class-name">Philosopher</span> <span class="token keyword">extends</span> <span class="token class-name">Thread</span> <span class="token punctuation">{</span>    Chopstick left<span class="token punctuation">;</span>    Chopstick right<span class="token punctuation">;</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">run</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 尝试获得左手筷子</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span>left<span class="token punctuation">.</span><span class="token function">tryLock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token keyword">try</span> <span class="token punctuation">{</span>                    <span class="token comment" spellcheck="true">// 尝试获得右手筷子</span>                    <span class="token keyword">if</span> <span class="token punctuation">(</span>right<span class="token punctuation">.</span><span class="token function">tryLock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                        <span class="token keyword">try</span> <span class="token punctuation">{</span>                            System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"eating..."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                            Thread<span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">1000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                        <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>                            right<span class="token punctuation">.</span><span class="token function">unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                        <span class="token punctuation">}</span>                    <span class="token punctuation">}</span>                <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>                    left<span class="token punctuation">.</span><span class="token function">unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token punctuation">}</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token keyword">class</span> <span class="token class-name">Chopstick</span> <span class="token keyword">extends</span> <span class="token class-name">ReentrantLock</span> <span class="token punctuation">{</span>    String name<span class="token punctuation">;</span>    <span class="token keyword">public</span> <span class="token function">Chopstick</span><span class="token punctuation">(</span>String name<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">this</span><span class="token punctuation">.</span>name <span class="token operator">=</span> name<span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> String <span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">return</span> <span class="token string">"筷子{"</span> <span class="token operator">+</span> name <span class="token operator">+</span> <span class="token string">'}'</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="实现原理-1"><a href="#实现原理-1" class="headerlink" title="实现原理"></a>实现原理</h4><p>参考下方同步器章节中ReentrantLock部分</p><h3 id="条件变量"><a href="#条件变量" class="headerlink" title="条件变量"></a>条件变量</h3><h4 id="基本使用-4"><a href="#基本使用-4" class="headerlink" title="基本使用"></a>基本使用</h4><p>synchronized 的条件变量，是当条件不满足时进入 WaitSet 等待；ReentrantLock 的条件变量比 synchronized 强大之处在于支持多个条件变量</p><p>ReentrantLock 类获取 Condition 对象：<code>public Condition newCondition()</code></p><p>Condition 类 API：</p><ul><li><code>void await()</code>：当前线程从运行状态进入等待状态，释放锁</li><li><code>void signal()</code>：唤醒一个等待在 Condition 上的线程，但是必须获得与该 Condition 相关的锁</li></ul><p>使用流程：</p><ul><li><p><strong>await &#x2F; signal 前需要获得锁</strong></p></li><li><p>await 执行后，会释放锁进入 ConditionObject 等待</p></li><li><p>await 的线程被唤醒去重新竞争 lock 锁</p></li><li><p><strong>线程在条件队列被打断会抛出中断异常</strong></p></li><li><p>竞争 lock 锁成功后，从 await 后继续执行</p></li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token keyword">throws</span> InterruptedException <span class="token punctuation">{</span>         <span class="token keyword">static</span> ReentrantLock lock <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ReentrantLock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">static</span> Condition waitCigaretteQueue <span class="token operator">=</span> lock<span class="token punctuation">.</span><span class="token function">newCondition</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">static</span> Condition waitbreakfastQueue <span class="token operator">=</span> lock<span class="token punctuation">.</span><span class="token function">newCondition</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">static</span> <span class="token keyword">volatile</span> <span class="token keyword">boolean</span> hasCigrette <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>    <span class="token keyword">static</span> <span class="token keyword">volatile</span> <span class="token keyword">boolean</span> hasBreakfast <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token keyword">throws</span> InterruptedException <span class="token punctuation">{</span>        <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span>            <span class="token keyword">try</span> <span class="token punctuation">{</span>                lock<span class="token punctuation">.</span><span class="token function">lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token operator">!</span>hasCigrette<span class="token punctuation">)</span> <span class="token punctuation">{</span>                    <span class="token keyword">try</span> <span class="token punctuation">{</span>                        waitCigaretteQueue<span class="token punctuation">.</span><span class="token function">await</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                    <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">InterruptedException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span>                        e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                    <span class="token punctuation">}</span>                <span class="token punctuation">}</span>                                System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"等到了它的烟"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>                lock<span class="token punctuation">.</span><span class="token function">unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span>            <span class="token keyword">try</span> <span class="token punctuation">{</span>                lock<span class="token punctuation">.</span><span class="token function">lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token operator">!</span>hasBreakfast<span class="token punctuation">)</span> <span class="token punctuation">{</span>                    <span class="token keyword">try</span> <span class="token punctuation">{</span>                        waitbreakfastQueue<span class="token punctuation">.</span><span class="token function">await</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                    <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">InterruptedException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span>                        e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                    <span class="token punctuation">}</span>                <span class="token punctuation">}</span>                 System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"等到了它的早餐"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>                lock<span class="token punctuation">.</span><span class="token function">unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token function">sendBreakfast</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token function">sendCigarette</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">sendCigarette</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        lock<span class="token punctuation">.</span><span class="token function">lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">try</span> <span class="token punctuation">{</span>             System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"送烟来了"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            hasCigrette <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>            waitCigaretteQueue<span class="token punctuation">.</span><span class="token function">signal</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>            lock<span class="token punctuation">.</span><span class="token function">unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span>    <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">sendBreakfast</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        lock<span class="token punctuation">.</span><span class="token function">lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">try</span> <span class="token punctuation">{</span>             System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"送早餐来了"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            hasBreakfast <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>            waitbreakfastQueue<span class="token punctuation">.</span><span class="token function">signal</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>            lock<span class="token punctuation">.</span><span class="token function">unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="实现原理-2"><a href="#实现原理-2" class="headerlink" title="实现原理"></a>实现原理</h4><p>参考下方同步器章节中ReentrantLock部分</p><h2 id="线程安全分析"><a href="#线程安全分析" class="headerlink" title="线程安全分析"></a>线程安全分析</h2><ol><li>成员变量和静态变量：</li></ol><ul><li>如果它们没有共享，则线程安全</li><li>如果它们被共享了，根据它们的状态是否能够改变，分两种情况：<ul><li>如果只有读操作，则线程安全</li><li>如果有读写操作，则这段代码是临界区，需要考虑线程安全问题</li></ul></li></ul><ol start="2"><li>局部变量：</li></ol><ul><li>局部变量是线程安全的</li><li>局部变量引用的对象不一定线程安全（逃逸分析）：<ul><li>如果该对象没有逃离方法的作用范围，它是线程安全的（每一个方法有一个栈帧）</li><li>如果该对象逃离方法的作用范围，需要考虑线程安全问题（暴露引用）</li></ul></li></ul><ol start="3"><li><p>线程安全类：String、Integer、StringBuffer、Random、Vector、Hashtable、java.util.concurrent 包</p></li><li><p><strong>每个方法是原子的，但多个方法的组合不是原子的</strong>，只能保证调用的方法内部安全：</p></li></ol><pre class="line-numbers language-java"><code class="language-java">Hashtable table <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Hashtable</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 线程1，线程2</span><span class="token keyword">if</span><span class="token punctuation">(</span>table<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">"key"</span><span class="token punctuation">)</span> <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>    table<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token string">"key"</span><span class="token punctuation">,</span> value<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">// get、put 两个方法分别是线程安全的，一起使用就是不安全的</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><img src="https://img.jwt1399.top/img/202212201746213.png" style="zoom:50%;" /><ol start="5"><li><p>无状态类（就是没有成员变量的类）是线程安全的</p></li><li><p>不可变类线程安全：String、Integer 等都是不可变类，<strong>内部的状态不可以改变</strong>，方法是线程安全</p></li></ol><ul><li><p>String 的 replace 等方法底层是新建一个对象，复制过去</p><pre class="line-numbers language-java"><code class="language-java">Map<span class="token operator">&lt;</span>String<span class="token punctuation">,</span>Object<span class="token operator">></span> map <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">HashMap</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 线程不安全</span>String S1 <span class="token operator">=</span> <span class="token string">"..."</span><span class="token punctuation">;</span>  <span class="token comment" spellcheck="true">// 线程安全</span><span class="token keyword">final</span> String S2 <span class="token operator">=</span> <span class="token string">"..."</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 线程安全</span>Date D1 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment" spellcheck="true">// 线程不安全</span><span class="token keyword">final</span> Date D2 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 线程不安全，final让D2引用的对象不能变，但对象的内容可以变</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre></li></ul><ol start="7"><li>抽象方法如果有参数，被重写后行为不确定可能造成线程不安全，被称之为外星方法：<code>public abstract foo(Student s);</code></li></ol><h2 id="同步模式"><a href="#同步模式" class="headerlink" title="同步模式"></a>同步模式</h2><h3 id="保护性暂停"><a href="#保护性暂停" class="headerlink" title="保护性暂停"></a>保护性暂停</h3><h4 id="单任务版"><a href="#单任务版" class="headerlink" title="单任务版"></a>单任务版</h4><p>Guarded Suspension，用在一个线程等待另一个线程的执行结果</p><ul><li>有一个结果需要从一个线程传递到另一个线程，让它们关联同一个 GuardedObject</li><li>如果有结果不断从一个线程到另一个线程那么可以使用消息队列（见生产者&#x2F;消费者）</li><li>JDK 中，join 的实现、Future 的实现，采用的就是此模式</li></ul><p><img src="https://img.jwt1399.top/img/202212271954511.png"></p><p>实现代码：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">GuardedObject</span> <span class="token punctuation">{</span>    <span class="token keyword">private</span> Object response<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//结果</span>    <span class="token comment" spellcheck="true">//获取结果</span>    <span class="token keyword">public</span> Object <span class="token function">get</span><span class="token punctuation">(</span><span class="token keyword">long</span> millis<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">synchronized</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 没有结果</span>            <span class="token keyword">while</span> <span class="token punctuation">(</span>response <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token keyword">try</span> <span class="token punctuation">{</span>                    <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">wait</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">InterruptedException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span>                    e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token punctuation">}</span>            <span class="token punctuation">}</span>            <span class="token keyword">return</span> response<span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">//产生结果</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">complete</span><span class="token punctuation">(</span>Object response<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">synchronized</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 条件满足，通知等待线程</span>            <span class="token keyword">this</span><span class="token punctuation">.</span>response <span class="token operator">=</span> response<span class="token punctuation">;</span>            lock<span class="token punctuation">.</span><span class="token function">notifyAll</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">//测试</span><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>       <span class="token comment" spellcheck="true">// 创建 GuardedObject 对象</span>    GuardedObject guardedObject <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">GuardedObject</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token comment" spellcheck="true">//线程1 等待 线程2 的下载结果</span>    <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span>        log<span class="token punctuation">.</span><span class="token function">debug</span><span class="token punctuation">(</span><span class="token string">"等待结果中"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        Object data <span class="token operator">=</span> guardedObject<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 获取结果</span>        log<span class="token punctuation">.</span><span class="token function">debug</span><span class="token punctuation">(</span><span class="token string">"结果为：{}"</span><span class="token punctuation">,</span>data<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>，<span class="token string">"t1"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token comment" spellcheck="true">//线程2 下载数据返回给 线程1 </span>      <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span>        log<span class="token punctuation">.</span><span class="token function">debug</span><span class="token punctuation">(</span><span class="token string">"执行下载"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        Object data <span class="token operator">=</span> <span class="token function">download</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 下载方法</span>        guardedObject<span class="token punctuation">.</span><span class="token function">complete</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span><span class="token punctuation">,</span><span class="token string">"t2"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="扩展"><a href="#扩展" class="headerlink" title="扩展"></a>扩展</h4><h4 id="多任务版"><a href="#多任务版" class="headerlink" title="多任务版"></a>多任务版</h4><h3 id="顺序输出"><a href="#顺序输出" class="headerlink" title="顺序输出"></a>顺序输出</h3><p>固定运行顺序，先输出 2 后 输出 1</p><h4 id="wait-notify-版"><a href="#wait-notify-版" class="headerlink" title="wait notify 版"></a>wait notify 版</h4><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token keyword">throws</span> InterruptedException <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 用来同步的对象</span>    <span class="token keyword">static</span> Object obj <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Object</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// t2 运行标记， 代表 t2 是否执行过</span>    <span class="token keyword">static</span> <span class="token keyword">boolean</span> t2runned <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>        Thread t1 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span>            <span class="token keyword">synchronized</span> <span class="token punctuation">(</span>obj<span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token comment" spellcheck="true">// 如果 t2 没有执行过</span>                <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token operator">!</span>t2runned<span class="token punctuation">)</span> <span class="token punctuation">{</span>                    <span class="token keyword">try</span> <span class="token punctuation">{</span>                        <span class="token comment" spellcheck="true">// t1 先等一会</span>                        obj<span class="token punctuation">.</span><span class="token function">wait</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                    <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">InterruptedException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span>                        e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                    <span class="token punctuation">}</span>                <span class="token punctuation">}</span>              System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"1"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>         <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token string">"t1"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>              Thread t2 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span>            <span class="token keyword">synchronized</span> <span class="token punctuation">(</span>obj<span class="token punctuation">)</span> <span class="token punctuation">{</span>                System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"2"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token comment" spellcheck="true">// 修改运行标记</span>                t2runned <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>                <span class="token comment" spellcheck="true">// 通知 obj 上等待的线程（可能有多个，因此需要用 notifyAll）</span>                obj<span class="token punctuation">.</span><span class="token function">notifyAll</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token string">"t2"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        t1<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        t2<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="Park-Unpark-版"><a href="#Park-Unpark-版" class="headerlink" title="Park Unpark 版"></a>Park Unpark 版</h4><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token keyword">throws</span> InterruptedException <span class="token punctuation">{</span>    Thread t1 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span>            LockSupport<span class="token punctuation">.</span><span class="token function">park</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"1"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token string">"t1"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      Thread t2 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span>            System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"2"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            LockSupport<span class="token punctuation">.</span><span class="token function">unpark</span><span class="token punctuation">(</span>t1<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token string">"t2"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      t1<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    t2<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="交替输出"><a href="#交替输出" class="headerlink" title="交替输出"></a>交替输出</h3><blockquote><p>线程 1 输出 a 5 次，线程 2 输出 b 5 次，线程 3 输出 c 5 次。现在要求输出 abcabcabcabcabc 怎么实现</p></blockquote><h4 id="wait-notify-版-1"><a href="#wait-notify-版-1" class="headerlink" title="wait notify 版"></a>wait notify 版</h4><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">/*输出内容  等待标记  下一个标记   a       1        2   b       2        3   c       3        1*/</span><span class="token keyword">class</span> <span class="token class-name">SyncWaitNotify</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 等待标记</span>    <span class="token keyword">private</span> <span class="token keyword">int</span> flag<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 循环次数</span>    <span class="token keyword">private</span> <span class="token keyword">int</span> loopNumber<span class="token punctuation">;</span>    <span class="token keyword">public</span> <span class="token function">SyncWaitNotify</span><span class="token punctuation">(</span><span class="token keyword">int</span> flag<span class="token punctuation">,</span> <span class="token keyword">int</span> loopNumber<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">this</span><span class="token punctuation">.</span>flag <span class="token operator">=</span> flag<span class="token punctuation">;</span>        <span class="token keyword">this</span><span class="token punctuation">.</span>loopNumber <span class="token operator">=</span> loopNumber<span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">print</span><span class="token punctuation">(</span><span class="token keyword">int</span> waitFlag<span class="token punctuation">,</span> <span class="token keyword">int</span> nextFlag<span class="token punctuation">,</span> String str<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> loopNumber<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token keyword">synchronized</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>flag <span class="token operator">!=</span> waitFlag<span class="token punctuation">)</span> <span class="token punctuation">{</span>                    <span class="token keyword">try</span> <span class="token punctuation">{</span>                        <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">wait</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                    <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">InterruptedException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span>                        e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                    <span class="token punctuation">}</span>                <span class="token punctuation">}</span>                System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">print</span><span class="token punctuation">(</span>str<span class="token punctuation">)</span><span class="token punctuation">;</span>                flag <span class="token operator">=</span> nextFlag<span class="token punctuation">;</span>                <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">notifyAll</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>        SyncWaitNotify syncWaitNotify <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">SyncWaitNotify</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span>            syncWaitNotify<span class="token punctuation">.</span><span class="token function">print</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token string">"a"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span>            syncWaitNotify<span class="token punctuation">.</span><span class="token function">print</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">,</span> <span class="token string">"b"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span>            syncWaitNotify<span class="token punctuation">.</span><span class="token function">print</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token string">"c"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="ReentrantLock-版"><a href="#ReentrantLock-版" class="headerlink" title="ReentrantLock 版"></a>ReentrantLock 版</h4><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">AwaitSignal</span> <span class="token keyword">extends</span> <span class="token class-name">ReentrantLock</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 循环次数</span>    <span class="token keyword">private</span> <span class="token keyword">int</span> loopNumber<span class="token punctuation">;</span>    <span class="token keyword">public</span> <span class="token function">AwaitSignal</span><span class="token punctuation">(</span><span class="token keyword">int</span> loopNumber<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">this</span><span class="token punctuation">.</span>loopNumber <span class="token operator">=</span> loopNumber<span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">// 参数1：打印内容 参数2：进入哪一间休息室 参数3：下一间休息室</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">print</span><span class="token punctuation">(</span>String str<span class="token punctuation">,</span> Condition current<span class="token punctuation">,</span> Condition next<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> loopNumber<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">try</span> <span class="token punctuation">{</span>                current<span class="token punctuation">.</span><span class="token function">await</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">print</span><span class="token punctuation">(</span>str<span class="token punctuation">)</span><span class="token punctuation">;</span>                next<span class="token punctuation">.</span><span class="token function">signal</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">InterruptedException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span>                e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>                <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token keyword">throws</span> InterruptedException <span class="token punctuation">{</span>        AwaitSignal as <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">AwaitSignal</span><span class="token punctuation">(</span><span class="token number">5</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// a,b,c分别的休息室</span>        Condition aWaitSet <span class="token operator">=</span> as<span class="token punctuation">.</span><span class="token function">newCondition</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        Condition bWaitSet <span class="token operator">=</span> as<span class="token punctuation">.</span><span class="token function">newCondition</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        Condition cWaitSet <span class="token operator">=</span> as<span class="token punctuation">.</span><span class="token function">newCondition</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span>            as<span class="token punctuation">.</span><span class="token function">print</span><span class="token punctuation">(</span><span class="token string">"a"</span><span class="token punctuation">,</span> aWaitSet<span class="token punctuation">,</span> bWaitSet<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span>            as<span class="token punctuation">.</span><span class="token function">print</span><span class="token punctuation">(</span><span class="token string">"b"</span><span class="token punctuation">,</span> bWaitSet<span class="token punctuation">,</span> cWaitSet<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span>            as<span class="token punctuation">.</span><span class="token function">print</span><span class="token punctuation">(</span><span class="token string">"c"</span><span class="token punctuation">,</span> cWaitSet<span class="token punctuation">,</span> aWaitSet<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                Thread<span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">1000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        as<span class="token punctuation">.</span><span class="token function">lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">try</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">//唤醒a休息室</span>            aWaitSet<span class="token punctuation">.</span><span class="token function">signal</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>            as<span class="token punctuation">.</span><span class="token function">unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="Park-Unpark-版-1"><a href="#Park-Unpark-版-1" class="headerlink" title="Park Unpark 版"></a>Park Unpark 版</h4><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">SyncPark</span> <span class="token punctuation">{</span>    <span class="token keyword">private</span> <span class="token keyword">int</span> loopNumber<span class="token punctuation">;</span>    <span class="token keyword">public</span> <span class="token function">SyncPark</span><span class="token punctuation">(</span><span class="token keyword">int</span> loopNumber<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">this</span><span class="token punctuation">.</span>loopNumber <span class="token operator">=</span> loopNumber<span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">print</span><span class="token punctuation">(</span>String str<span class="token punctuation">,</span> Thread next<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> loopNumber<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            LockSupport<span class="token punctuation">.</span><span class="token function">park</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">print</span><span class="token punctuation">(</span>str<span class="token punctuation">)</span><span class="token punctuation">;</span>            LockSupport<span class="token punctuation">.</span><span class="token function">unpark</span><span class="token punctuation">(</span>next<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span>    <span class="token keyword">static</span>  Thread t1<span class="token punctuation">,</span> t2<span class="token punctuation">,</span> t3<span class="token punctuation">;</span>      <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>        SyncPark syncPark <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">SyncPark</span><span class="token punctuation">(</span><span class="token number">5</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        t1 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span>            syncPark<span class="token punctuation">.</span><span class="token function">print</span><span class="token punctuation">(</span><span class="token string">"a"</span><span class="token punctuation">,</span> t2<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        t2 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span>            syncPark<span class="token punctuation">.</span><span class="token function">print</span><span class="token punctuation">(</span><span class="token string">"b"</span><span class="token punctuation">,</span> t3<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        t3 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span>            syncPark<span class="token punctuation">.</span><span class="token function">print</span><span class="token punctuation">(</span><span class="token string">"c\n"</span><span class="token punctuation">,</span> t1<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        t1<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        t2<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        t3<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        LockSupport<span class="token punctuation">.</span><span class="token function">unpark</span><span class="token punctuation">(</span>t1<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h1 id="③内存"><a href="#③内存" class="headerlink" title="③内存"></a>③内存</h1><h2 id="❶JMM"><a href="#❶JMM" class="headerlink" title="❶JMM"></a>❶JMM</h2><h3 id="JMM简介"><a href="#JMM简介" class="headerlink" title="JMM简介"></a>JMM简介</h3><p>Java 内存模型是 Java Memory Model（JMM），本身是一种<strong>抽象的概念</strong>，实际上并不存在，描述的是一组规则或规范，通过这组规范定义了程序中各个变量（包括实例字段，静态字段和构成数组对象的元素）的访问方式</p><p>JMM 作用：</p><ul><li>屏蔽各种硬件和操作系统的内存访问差异，实现让 Java 程序在各种平台下都能达到一致的内存访问效果</li><li>规定了线程和内存之间的一些关系</li></ul><p><img src="https://img.jwt1399.top/img/202212291444042.png"></p><p>根据 JMM 的设计，系统存在一个<code>主内存（Main Memory）</code>，Java 中所有变量都存储在主存中，对于所有线程都是共享的；每条线程都有自己的<code>工作内存（Working Memory）</code>，工作内存中保存的是主存中某些<strong>变量的拷贝</strong>，工作内存存储在高速缓存或者寄存器中，线程对所有变量的操作都是先对变量进行拷贝，然后在工作内存中进行，不能直接操作主内存中的变量；线程之间无法相互直接访问，线程间的通信（传递）必须通过主内存来完成</p><p>主内存和工作内存：</p><ul><li>主内存：计算机的内存，Java 中所有变量都存储在主内存中，对于所有线程都是共享的。</li><li>工作内存：存储的是主内存中某些<strong>变量的拷贝</strong>，工作内存存储在高速缓存或者寄存器中</li></ul><p>处理器上的寄存器的读写的速度比内存快几个数量级，为了解决这种速度矛盾，在它们之间加入了高速缓存。加入高速缓存带来了一个新的问题：缓存一致性【当多个处理器运算任务都涉及到同一块主内存区域的时候，将可能导致各自的缓存数据不一样】，需要一些协议来解决这个问题。</p><img src="https://img.jwt1399.top/img/202212291454203.png" style="zoom:80%;" /><p><strong>JVM 和 JMM 之间的关系</strong>：JMM 中的主内存、工作内存与 JVM 中的 Java 堆、栈、方法区等并不是同一个层次的内存划分，这两者基本上是没有关系的，如果两者一定要勉强对应起来：</p><ul><li>主内存主要对应于 Java 堆中的对象实例数据部分，而工作内存则对应于虚拟机栈中的部分区域</li><li>从更低层次上说，主内存直接对应于物理硬件的内存，工作内存对应寄存器和高速缓存</li></ul><h3 id="缓存机制"><a href="#缓存机制" class="headerlink" title="缓存机制"></a>缓存机制</h3><p>在计算机系统中，CPU 高速缓存（CPU Cache，简称缓存）是用于减少处理器访问内存所需平均时间的部件；在存储体系中位于自顶向下的第二层，仅次于 CPU 寄存器；其容量远小于内存，但速度却可以接近处理器的频率</p><p>CPU 处理器速度远远大于在主内存中的，为了解决速度差异，在它们之间架设了多级缓存，如 L1、L2、L3 级别的缓存，这些缓存离 CPU 越近就越快，将频繁操作的数据缓存到这里，加快访问速度</p><img src="../images/Java-JUC/JMM-CPU缓存结构.png" style="zoom: 50%;" /><p><strong>缓存使用</strong></p><p>当处理器发出内存访问请求时，会先查看缓存内是否有请求数据，如果存在，则不用访问内存直接返回该数据；如果不存在，则要先把内存中的相应数据载入缓存，再将其返回处理器</p><p>缓存之所以有效，主要因为程序运行时对内存的访问呈现局部性（Locality）特征。既包括空间局部性（Spatial Locality），也包括时间局部性（Temporal Locality），有效利用这种局部性，缓存可以达到极高的命中率</p><p><strong>处理机制</strong></p><p>单核 CPU 处理器会自动保证基本内存操作的原子性</p><p>多核 CPU 处理器，每个 CPU 处理器内维护了一块内存，每个内核内部维护着一块缓存，当多线程并发读写时，就会出现缓存数据不一致的情况。处理器提供：总线锁定和缓存锁定来解决</p><h3 id="内存交互"><a href="#内存交互" class="headerlink" title="内存交互"></a>内存交互</h3><p>Java 内存模型定义了 8 个操作来完成主内存和工作内存的交互操作，每个操作都是<strong>原子</strong>的</p><p>非原子协定：没有被 volatile 修饰的 long、double 外，默认按照两次 32 位的操作</p><img src="https://img.jwt1399.top/img/202212291445582.png" style="zoom: 67%;" /><ul><li><p>read：作用于主内存，把一个变量的值从主内存传输到工作内存中</p></li><li><p>load：作用于工作内存，在 read 之后执行，把 read 得到的值放入工作内存的变量副本中</p></li><li><p>use：作用于工作内存，把工作内存中一个变量的值传递给<strong>执行引擎</strong>，每当遇到一个使用到变量的操作时都要使用该指令</p></li><li><p>assign：作用于工作内存，把从执行引擎接收到的一个值赋给工作内存的变量</p></li><li><p>store：作用于工作内存，把工作内存的一个变量的值传送到主内存中</p></li><li><p>write：作用于主内存，在 store 之后执行，把 store 得到的值放入主内存的变量中</p></li><li><p>lock：作用于主内存，将一个变量标识为被一个线程独占状态（对应 monitorenter）</p></li><li><p>unclock：作用于主内存，将一个变量从独占状态释放出来，释放后的变量才可以被其他线程锁定（对应 monitorexit）</p></li></ul><h3 id="三大特性"><a href="#三大特性" class="headerlink" title="三大特性"></a>三大特性</h3><h4 id="可见性"><a href="#可见性" class="headerlink" title="可见性"></a>可见性</h4><p>可见性：是指当多个线程访问同一个变量时，一个线程修改了这个变量的值，其他线程能够立即看得到修改的值</p><p>存在不可见问题的根本原因是由于缓存的存在，线程持有的是共享变量的副本，无法感知其他线程对于共享变量的更改，导致读取的值不是最新的。但是 final 修饰的变量是<strong>不可变</strong>的，就算有缓存，也不会存在不可见的问题</p><p>main 线程对 run 变量的修改对于 t 线程不可见，导致了 t 线程无法停止：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">static</span> <span class="token keyword">boolean</span> run <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//添加volatile即可解决不可见的问题</span><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token keyword">throws</span> InterruptedException <span class="token punctuation">{</span>    Thread t <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">></span><span class="token punctuation">{</span>        <span class="token keyword">while</span><span class="token punctuation">(</span>run<span class="token punctuation">)</span><span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// ....</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    t<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    run <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>  <span class="token comment" spellcheck="true">//线程t按理应该停下来</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">// 线程t不会如预想的停下来</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>原因：</p><ul><li>初始状态， t 线程刚开始从主内存读取了 run 的值到工作内存</li></ul><img src="https://img.jwt1399.top/img/202212282148547.png" style="zoom:50%;" /><ul><li>因为 t 线程要频繁从主内存中读取 run 的值，JIT 编译器会将 run 的值缓存至自己工作内存中的高速缓存中，减少对主存中 run 的访问，提高效率</li></ul><img src="https://img.jwt1399.top/img/202212282148347.png"  /><ul><li>1 秒之后，main 线程修改了 run 的值，并同步至主存，而 t 是从自己工作内存中的高速缓存中读取这个变量的值，结果永远是旧值</li></ul><p><img src="https://img.jwt1399.top/img/202212282149428.png"></p><h4 id="原子性"><a href="#原子性" class="headerlink" title="原子性"></a>原子性</h4><p>原子性：不可分割，完整性，也就是说某个线程正在做某个具体业务时，中间不可以被分割，要么同时成功，要么同时失败，保证指令不会受到线程上下文切换的影响 </p><p>定义原子操作的使用规则：</p><ol><li>不允许 read 和 load、store 和 write 操作之一单独出现，必须顺序执行，但是不要求连续</li><li>不允许一个线程丢弃 assign 操作，必须同步回主存</li><li>不允许一个线程无原因地（没有发生过任何 assign 操作）把数据从工作内存同步会主内存中</li><li>一个新的变量只能在主内存中诞生，不允许在工作内存中直接使用一个未被初始化（assign 或者 load）的变量，即对一个变量实施 use 和 store 操作之前，必须先自行 assign 和 load 操作</li><li>一个变量在同一时刻只允许一条线程对其进行 lock 操作，但 lock 操作可以被同一线程重复执行多次，多次执行 lock 后，只有<strong>执行相同次数的 unlock</strong> 操作，变量才会被解锁，<strong>lock 和 unlock 必须成对出现</strong></li><li>如果对一个变量执行 lock 操作，将会<strong>清空工作内存中此变量的值</strong>，在执行引擎使用这个变量之前需要重新从主存加载</li><li>如果一个变量事先没有被 lock 操作锁定，则不允许执行 unlock 操作，也不允许去 unlock 一个被其他线程锁定的变量</li><li>对一个变量执行 unlock 操作之前，必须<strong>先把此变量同步到主内存</strong>中（执行 store 和 write 操作）</li></ol><h4 id="有序性"><a href="#有序性" class="headerlink" title="有序性"></a>有序性</h4><p>有序性：在本线程内观察，所有操作都是有序的；在一个线程观察另一个线程，所有操作都是无序的，无序是因为发生了指令重排序</p><p>CPU 的基本工作是执行存储的指令序列，即程序，程序的执行过程实际上是不断地取出指令、分析指令、执行指令的过程，为了提高性能，编译器和处理器会对指令重排，一般分为以下三种：</p><pre class="line-numbers language-java"><code class="language-java">源代码 <span class="token operator">-</span><span class="token operator">></span> 编译器优化的重排 <span class="token operator">-</span><span class="token operator">></span> 指令并行的重排 <span class="token operator">-</span><span class="token operator">></span> 内存系统的重排 <span class="token operator">-</span><span class="token operator">></span> 最终执行指令<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>现代 CPU 支持多级指令流水线，几乎所有的冯•诺伊曼型计算机的 CPU，其工作都可以分为 5 个阶段：取指令、指令译码、执行指令、访存取数和结果写回，可以称之为<strong>五级指令流水线</strong>。CPU 可以在一个时钟周期内，同时运行五条指令的<strong>不同阶段</strong>（每个线程不同的阶段），本质上流水线技术并不能缩短单条指令的执行时间，但变相地提高了指令地吞吐率</p><p>处理器在进行重排序时，必须要考虑<strong>指令之间的数据依赖性</strong></p><ul><li>单线程环境也存在指令重排，由于存在依赖性，最终执行结果和代码顺序的结果一致</li><li>多线程环境中线程交替执行，由于编译器优化重排，会获取其他线程处在不同阶段的指令同时执行</li></ul><p>补充知识：</p><ul><li>指令周期是取出一条指令并执行这条指令的时间，一般由若干个机器周期组成</li><li>机器周期也称为 CPU 周期，一条指令的执行过程划分为若干个阶段（如取指、译码、执行等），每一阶段完成一个基本操作，完成一个基本操作所需要的时间称为机器周期</li><li>振荡周期指周期性信号作周期性重复变化的时间间隔</li></ul><h2 id="❷volatile"><a href="#❷volatile" class="headerlink" title="❷volatile"></a>❷volatile</h2><h3 id="同步机制"><a href="#同步机制" class="headerlink" title="同步机制"></a>同步机制</h3><p>volatile 是 Java 虚拟机提供的<strong>轻量级</strong>的同步机制（三大特性）</p><ul><li>保证可见性</li><li>不保证原子性</li><li>保证有序性（禁止指令重排）</li></ul><p>性能：volatile 修饰的变量进行读操作与普通变量几乎没什么差别，但是写操作相对慢一些，因为需要在本地代码中插入很多内存屏障来保证指令不会发生乱序执行，但是开销比锁要小（因此适合一写多读的场景）</p><p>synchronized 无法禁止指令重排和处理器优化，为什么可以保证有序性可见性</p><ul><li>加了锁之后，只能有一个线程获得到了锁，获得不到锁的线程就要阻塞，所以同一时间只有一个线程执行，相当于单线程，由于数据依赖性的存在，单线程的指令重排是没有问题的</li><li>线程加锁前，将<strong>清空工作内存</strong>中共享变量的值，使用共享变量时需要从主内存中重新读取最新的值；线程解锁前，必须把共享变量的最新值<strong>刷新到主内存</strong>中</li></ul><h3 id="指令重排"><a href="#指令重排" class="headerlink" title="指令重排"></a>指令重排</h3><p>volatile 修饰的变量，可以禁用指令重排</p><p>指令重排实例：</p><ul><li><p>example 1：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">mySort</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">int</span> x <span class="token operator">=</span> <span class="token number">11</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//语句1</span>    <span class="token keyword">int</span> y <span class="token operator">=</span> <span class="token number">12</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//语句2  谁先执行效果一样</span>    x <span class="token operator">=</span> x <span class="token operator">+</span> <span class="token number">5</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//语句3</span>    y <span class="token operator">=</span> x <span class="token operator">*</span> x<span class="token punctuation">;</span><span class="token comment" spellcheck="true">//语句4</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>执行顺序是：1 2 3 4、2 1 3 4、1 3 2 4</p><p>指令重排也有限制不会出现：4321，语句 4 需要依赖于 y 以及 x 的申明，因为存在数据依赖，无法首先执行</p></li><li><p>example 2：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">int</span> num <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span><span class="token keyword">boolean</span> ready <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 线程1 执行此方法</span><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">actor1</span><span class="token punctuation">(</span>I_Result r<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">if</span><span class="token punctuation">(</span>ready<span class="token punctuation">)</span> <span class="token punctuation">{</span>        r<span class="token punctuation">.</span>r1 <span class="token operator">=</span> num <span class="token operator">+</span> num<span class="token punctuation">;</span>    <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>        r<span class="token punctuation">.</span>r1 <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">// 线程2 执行此方法</span><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">actor2</span><span class="token punctuation">(</span>I_Result r<span class="token punctuation">)</span> <span class="token punctuation">{</span>    num <span class="token operator">=</span> <span class="token number">2</span><span class="token punctuation">;</span>    ready <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>情况一：线程 1 先执行，ready &#x3D; false，结果为 r.r1 &#x3D; 1</p><p>情况二：线程 2 先执行 num &#x3D; 2，但还没执行 ready &#x3D; true，线程 1 执行，结果为 r.r1 &#x3D; 1</p><p>情况三：线程 2 先执行 ready &#x3D; true，线程 1 执行，进入 if 分支结果为 r.r1 &#x3D; 4</p><p>情况四：线程 2 执行 ready &#x3D; true，切换到线程 1，进入 if 分支为 r.r1 &#x3D; 0，再切回线程 2 执行 num &#x3D; 2，发生指令重排</p></li></ul><h3 id="底层原理"><a href="#底层原理" class="headerlink" title="底层原理"></a>底层原理</h3><h4 id="缓存一致"><a href="#缓存一致" class="headerlink" title="缓存一致"></a>缓存一致</h4><p>使用 volatile 修饰的共享变量，总线会开启 <strong>CPU 总线嗅探机制</strong>来解决 JMM 缓存一致性问题，也就是共享变量在多线程中可见性的问题，实现 MESI 缓存一致性协议</p><p>底层是通过汇编 lock 前缀指令，共享变量加了 lock 前缀指令就会进行缓存锁定，在线程修改完共享变量后写回主存，其他的 CPU 核心上运行的线程根据总线嗅探机制会修改其共享变量为失效状态，读取时会重新从主内存中读取最新的数据</p><p>lock 前缀指令就相当于内存屏障，Memory Barrier（Memory Fence）</p><ul><li>对 volatile 变量的写指令后会加入写屏障</li><li>对 volatile 变量的读指令前会加入读屏障</li></ul><p>内存屏障有三个作用：</p><ul><li>确保对内存的读-改-写操作原子执行</li><li>阻止屏障两侧的指令重排序</li><li>强制把缓存中的脏数据写回主内存，让缓存行中相应的数据失效</li></ul><h4 id="内存屏障"><a href="#内存屏障" class="headerlink" title="内存屏障"></a>内存屏障</h4><p>保证<strong>可见性</strong>：</p><ul><li><p>写屏障（sfence，Store Barrier）保证在该屏障之前的，对共享变量的改动，都同步到主存当中</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">actor2</span><span class="token punctuation">(</span>I_Result r<span class="token punctuation">)</span> <span class="token punctuation">{</span>    num <span class="token operator">=</span> <span class="token number">2</span><span class="token punctuation">;</span>    ready <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// ready 是 volatile 赋值带写屏障</span>    <span class="token comment" spellcheck="true">// 写屏障</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>读屏障（lfence，Load Barrier）保证在该屏障之后的，对共享变量的读取，从主存刷新变量值，加载的是主存中最新数据</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">actor1</span><span class="token punctuation">(</span>I_Result r<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 读屏障</span>    <span class="token comment" spellcheck="true">// ready 是 volatile 读取值带读屏障</span>    <span class="token keyword">if</span><span class="token punctuation">(</span>ready<span class="token punctuation">)</span> <span class="token punctuation">{</span>        r<span class="token punctuation">.</span>r1 <span class="token operator">=</span> num <span class="token operator">+</span> num<span class="token punctuation">;</span>    <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>        r<span class="token punctuation">.</span>r1 <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><img src="https://img.jwt1399.top/img/202212291520278.png" style="zoom:67%;" /></li><li><p>全能屏障：mfence（modify&#x2F;mix Barrier），兼具 写屏障 和 读屏障 的功能</p></li></ul><p>保证<strong>有序性</strong>：</p><ul><li>写屏障会确保指令重排序时，不会将写屏障之前的代码排在写屏障之后</li><li>读屏障会确保指令重排序时，不会将读屏障之后的代码排在读屏障之前</li></ul><p>不能解决指令交错：</p><ul><li><p>写屏障仅仅是保证之后的读能够读到最新的结果，但不能保证其他线程的读跑到写屏障之前</p></li><li><p>有序性的保证也只是保证了本线程内相关代码不被重排序</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">volatile</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span><span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span>i<span class="token operator">++</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span>i<span class="token operator">--</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>i++ 反编译后的指令：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token number">0</span><span class="token operator">:</span> iconst_1<span class="token comment" spellcheck="true">// 当int取值 -1~5 时，JVM采用iconst指令将常量压入栈中</span><span class="token number">1</span><span class="token operator">:</span> istore_1<span class="token comment" spellcheck="true">// 将操作数栈顶数据弹出，存入局部变量表的 slot 1</span><span class="token number">2</span><span class="token operator">:</span> iinc<span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">1</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><img src="../images/Java-JUC/JMM-volatile不能保证原子性.png" style="zoom:67%;" /></li></ul><h5 id="交互规则"><a href="#交互规则" class="headerlink" title="交互规则"></a>交互规则</h5><p>对于 volatile 修饰的变量：</p><ul><li>线程对变量的 use 与 load、read 操作是相关联的，所以变量使用前必须先从主存加载</li><li>线程对变量的 assign 与 store、write 操作是相关联的，所以变量使用后必须同步至主存</li><li>线程 1 和线程 2 谁先对变量执行 read 操作，就会先进行 write 操作，防止指令重排</li></ul><h3 id="双端检锁"><a href="#双端检锁" class="headerlink" title="双端检锁"></a>双端检锁</h3><h4 id="检锁机制"><a href="#检锁机制" class="headerlink" title="检锁机制"></a>检锁机制</h4><p>Double-Checked Locking：双端检锁机制</p><p>DCL（双端检锁）机制不一定是线程安全的，原因是有指令重排的存在，加入 volatile 可以禁止指令重排</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token keyword">class</span> <span class="token class-name">Singleton</span> <span class="token punctuation">{</span>    <span class="token keyword">private</span> <span class="token function">Singleton</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span>    <span class="token keyword">private</span> <span class="token keyword">static</span> Singleton INSTANCE <span class="token operator">=</span> null<span class="token punctuation">;</span>        <span class="token keyword">public</span> <span class="token keyword">static</span> Singleton <span class="token function">getInstance</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>INSTANCE <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// t2，这里的判断不是线程安全的</span>            <span class="token comment" spellcheck="true">// 首次访问会同步，而之后的使用没有 synchronized</span>            <span class="token keyword">synchronized</span><span class="token punctuation">(</span>Singleton<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token comment" spellcheck="true">// 这里是线程安全的判断，防止其他线程在当前线程等待锁的期间完成了初始化</span>                <span class="token keyword">if</span> <span class="token punctuation">(</span>INSTANCE <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>                     INSTANCE <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Singleton</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token punctuation">}</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> INSTANCE<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>不锁 INSTANCE 的原因：</p><ul><li>INSTANCE 要重新赋值</li><li>INSTANCE 是 null，线程加锁之前需要获取对象的引用，设置对象头，null 没有引用</li></ul><p>实现特点： </p><ul><li>懒惰初始化</li><li>首次使用 getInstance() 才使用 synchronized 加锁，后续使用时无需加锁</li><li>第一个 if 使用了 INSTANCE 变量，是在同步块之外，但在多线程环境下会产生问题</li></ul><h4 id="DCL问题"><a href="#DCL问题" class="headerlink" title="DCL问题"></a>DCL问题</h4><p>getInstance 方法对应的字节码为：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token number">0</span><span class="token operator">:</span> getstatic #<span class="token number">2</span> <span class="token comment" spellcheck="true">// Field INSTANCE:Ltest/Singleton;</span><span class="token number">3</span><span class="token operator">:</span> ifnonnull <span class="token number">37</span><span class="token number">6</span><span class="token operator">:</span> ldc #<span class="token number">3</span> <span class="token comment" spellcheck="true">// class test/Singleton</span><span class="token number">8</span><span class="token operator">:</span> dup<span class="token number">9</span><span class="token operator">:</span> astore_0<span class="token number">10</span><span class="token operator">:</span> monitorenter<span class="token number">11</span><span class="token operator">:</span> getstatic #<span class="token number">2</span> <span class="token comment" spellcheck="true">// Field INSTANCE:Ltest/Singleton;</span><span class="token number">14</span><span class="token operator">:</span> ifnonnull <span class="token number">27</span><span class="token number">17</span><span class="token operator">:</span> <span class="token keyword">new</span> #<span class="token number">3</span> <span class="token comment" spellcheck="true">// class test/Singleton</span><span class="token number">20</span><span class="token operator">:</span> dup<span class="token number">21</span><span class="token operator">:</span> invokespecial #<span class="token number">4</span> <span class="token comment" spellcheck="true">// Method "&lt;init>":()V</span><span class="token number">24</span><span class="token operator">:</span> putstatic #<span class="token number">2</span> <span class="token comment" spellcheck="true">// Field INSTANCE:Ltest/Singleton;</span><span class="token number">27</span><span class="token operator">:</span> aload_0<span class="token number">28</span><span class="token operator">:</span> monitorexit<span class="token number">29</span><span class="token operator">:</span> <span class="token keyword">goto</span> <span class="token number">37</span><span class="token number">32</span><span class="token operator">:</span> astore_1<span class="token number">33</span><span class="token operator">:</span> aload_0<span class="token number">34</span><span class="token operator">:</span> monitorexit<span class="token number">35</span><span class="token operator">:</span> aload_1<span class="token number">36</span><span class="token operator">:</span> athrow<span class="token number">37</span><span class="token operator">:</span> getstatic #<span class="token number">2</span> <span class="token comment" spellcheck="true">// Field INSTANCE:Ltest/Singleton;</span><span class="token number">40</span><span class="token operator">:</span> areturn<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>17 表示创建对象，将对象引用入栈 </li><li>20 表示复制一份对象引用，引用地址</li><li>21 表示利用一个对象引用，调用构造方法初始化对象</li><li>24 表示利用一个对象引用，赋值给 static INSTANCE</li></ul><p><strong>步骤 21 和 24 之间不存在数据依赖关系</strong>，而且无论重排前后，程序的执行结果在单线程中并没有改变，因此这种重排优化是允许的</p><ul><li>关键在于 0:  getstatic 这行代码在 monitor 控制之外，可以越过 monitor 读取 INSTANCE 变量的值</li><li>当其他线程访问 INSTANCE 不为 null 时，由于 INSTANCE 实例未必已初始化，那么 t2 拿到的是将是一个未初始化完毕的单例返回，这就造成了线程安全的问题</li></ul><p><img src="https://img.jwt1399.top/img/202212291533213.png"></p><h4 id="解决方法"><a href="#解决方法" class="headerlink" title="解决方法"></a>解决方法</h4><p>指令重排只会保证串行语义的执行一致性（单线程），但并不会关系多线程间的语义一致性</p><p>引入 volatile，来保证出现指令重排的问题，从而保证单例模式的线程安全性：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">volatile</span> SingletonDemo INSTANCE <span class="token operator">=</span> null<span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><h3 id="happens-before"><a href="#happens-before" class="headerlink" title="happens-before"></a>happens-before</h3><p>Java 内存模型具备一些先天的“有序性”，即不需要通过任何同步手段（volatile、synchronized 等）就能够得到保证的安全，这个通常也称为 happens-before【先行发生】 原则，它是可见性与有序性的一套规则总结</p><p>不符合 happens-before 规则，JMM 并不能保证一个线程的可见性和有序性</p><ol><li><p>程序次序规则 (Program Order Rule)：一个线程内，逻辑上书写在前面的操作先行发生于书写在后面的操作 ，因为多个操作之间有先后依赖关系，则不允许对这些操作进行重排序</p></li><li><p>锁定规则 (Monitor Lock Rule)：一个 unlock 操作先行发生于后面（时间的先后）对同一个锁的 lock 操作。线程解锁 m 之前对变量的写（解锁前会刷新到主内存中），对于接下来对 m 加锁的其它线程对该变量的读可见</p><pre class="line-numbers language-java"><code class="language-java">  <span class="token keyword">static</span> <span class="token keyword">int</span> x<span class="token punctuation">;</span>  <span class="token keyword">static</span> Object m <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Object</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">></span><span class="token punctuation">{</span>      <span class="token keyword">synchronized</span><span class="token punctuation">(</span>m<span class="token punctuation">)</span> <span class="token punctuation">{</span>              x <span class="token operator">=</span> <span class="token number">10</span><span class="token punctuation">;</span>      <span class="token punctuation">}</span>  <span class="token punctuation">}</span><span class="token punctuation">,</span><span class="token string">"t1"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">></span><span class="token punctuation">{</span>      <span class="token keyword">synchronized</span><span class="token punctuation">(</span>m<span class="token punctuation">)</span> <span class="token punctuation">{</span>              System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>x<span class="token punctuation">)</span><span class="token punctuation">;</span>          <span class="token punctuation">}</span>  <span class="token punctuation">}</span><span class="token punctuation">,</span><span class="token string">"t2"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>volatile 变量规则  (Volatile Variable Rule)：线程对 volatile 变量的写，对接下来其它线程对该变量的读可见</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">volatile</span> <span class="token keyword">static</span> <span class="token keyword">int</span> x<span class="token punctuation">;</span><span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">></span><span class="token punctuation">{</span>        x <span class="token operator">=</span> <span class="token number">10</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token punctuation">,</span><span class="token string">"t1"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">></span><span class="token punctuation">{</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>x<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token punctuation">,</span><span class="token string">"t2"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>传递规则 (Transitivity)：具有传递性，如果操作 A 先行发生于操作 B，而操作 B 又先行发生于操作 C，则可以得出操作 A 先行发生于操作 C</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">volatile</span> <span class="token keyword">static</span> <span class="token keyword">int</span> x<span class="token punctuation">;</span><span class="token keyword">static</span> <span class="token keyword">int</span> y<span class="token punctuation">;</span><span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">></span><span class="token punctuation">{</span>  y <span class="token operator">=</span> <span class="token number">10</span><span class="token punctuation">;</span>  x <span class="token operator">=</span> <span class="token number">20</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token punctuation">,</span><span class="token string">"t1"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">></span><span class="token punctuation">{</span>  <span class="token comment" spellcheck="true">// x=20 对 t2 可见, 同时 y=10 也对 t2 可见</span>  System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>x<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token punctuation">,</span><span class="token string">"t2"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>线程启动规则 (Thread Start Rule)：线程 start 方法前对变量的写，对该线程开始后对该变量的读可见</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">static</span> <span class="token keyword">int</span> x <span class="token operator">=</span> <span class="token number">10</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//线程 start 前对变量的写，对该线程开始后对该变量的读可见</span><span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">></span><span class="token punctuation">{</span>  System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>x<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token punctuation">,</span><span class="token string">"t1"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>线程中断规则 (Thread Interruption Rule)：对线程 interrupt() 方法的调用先行发生于被中断线程的代码检测到中断事件的发生。线程 t1 打断 t2（interrupt）前对变量的写，对于其他线程得知 t2 被打断后对变量的读可见（通过t2.interrupted 或 t2.isInterrupted）</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">static</span> <span class="token keyword">int</span> x<span class="token punctuation">;</span><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>        Thread t2 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">></span><span class="token punctuation">{</span>        <span class="token keyword">while</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>          <span class="token keyword">if</span><span class="token punctuation">(</span>Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">isInterrupted</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>          System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>x<span class="token punctuation">)</span><span class="token punctuation">;</span>          <span class="token keyword">break</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span><span class="token punctuation">,</span><span class="token string">"t2"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    t2<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">></span><span class="token punctuation">{</span>            <span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            x <span class="token operator">=</span> <span class="token number">10</span><span class="token punctuation">;</span>            t2<span class="token punctuation">.</span><span class="token function">interrupt</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">,</span><span class="token string">"t1"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">while</span><span class="token punctuation">(</span><span class="token operator">!</span>t2<span class="token punctuation">.</span><span class="token function">isInterrupted</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            Thread<span class="token punctuation">.</span><span class="token function">yield</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>x<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>线程终止规则 (Thread Termination Rule)：线程结束前对变量的写，对其它线程得知它结束后的读可见（比如其它线程调用 t1.isAlive() 或 t1.join()等待它结束）</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">static</span> <span class="token keyword">int</span> x<span class="token punctuation">;</span>Thread t1 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">></span><span class="token punctuation">{</span>        x <span class="token operator">=</span> <span class="token number">10</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token punctuation">,</span><span class="token string">"t1"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>t1<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>t1<span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>x<span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>对象终结规则（Finaizer Rule）：一个对象的初始化完成（构造函数执行结束）先行发生于它的 finalize() 方法的开始。对变量默认值（0，false，null）的写，对其它线程对该变量的读可见</p></li></ol><h1 id="④无锁"><a href="#④无锁" class="headerlink" title="④无锁"></a>④无锁</h1><h2 id="❶CAS"><a href="#❶CAS" class="headerlink" title="❶CAS"></a>❶CAS</h2><h3 id="原理-1"><a href="#原理-1" class="headerlink" title="原理"></a><strong>原理</strong></h3><p>无锁编程：Lock Free</p><p>CAS 的全称是 Compare-And-Swap，是 <strong>CPU 并发原语</strong></p><ul><li>CAS 并发原语体现在 Java 语言中就是 sun.misc.Unsafe 类的各个方法，调用 UnSafe 类中的 CAS 方法，JVM 会实现出 CAS 汇编指令，这是一种完全依赖于硬件的功能，实现了原子操作</li><li>CAS 是一种系统原语，原语属于操作系统范畴，是由若干条指令组成 ，用于完成某个功能的一个过程，并且原语的执行必须是连续的，执行过程中不允许被中断，所以 CAS 是一条 CPU 的原子指令，不会造成数据不一致的问题，是线程安全的</li><li><strong>CAS 必须借助 volatile 才能读取到共享变量的最新值来实现比较并交换的效果</strong></li></ul><p>底层原理：CAS 的底层是 <code>lock cmpxchg</code> 指令（X86 架构），在单核和多核 CPU 下都能够保证比较交换的原子性</p><ul><li><p>程序是在单核处理器上运行，会省略 lock 前缀，单处理器自身会维护处理器内的顺序一致性，不需要 lock 前缀的内存屏障效果</p></li><li><p>程序是在多核处理器上运行，会为 cmpxchg 指令加上 lock 前缀。当某个核执行到带 lock 的指令时，CPU 会执行<strong>总线锁定或缓存锁定</strong>，将修改的变量写入到主存，这个过程不会被线程的调度机制所打断，保证了多个线程对内存操作的原子性</p></li></ul><p>作用：比较当前工作内存中的值和主物理内存中的值，如果相同则执行规定操作，否则继续比较直到主内存和工作内存的值一致为止</p><p>CAS 特点：</p><ul><li>CAS 体现的是<strong>无锁并发、无阻塞并发</strong>，线程不会陷入阻塞，线程不需要频繁切换状态（上下文切换，系统调用）</li><li>CAS 是基于乐观锁的思想</li></ul><p>CAS 缺点：</p><ul><li>执行的是循环操作，如果比较不成功一直在循环，最差的情况某个线程一直取到的值和预期值都不一样，就会无限循环导致饥饿，<strong>使用 CAS 线程数不要超过 CPU 的核心数</strong>，采用分段 CAS 和自动迁移机制</li><li>只能保证一个共享变量的原子操作<ul><li>对于一个共享变量执行操作时，可以通过循环 CAS 的方式来保证原子操作</li><li>对于多个共享变量操作时，循环 CAS 就无法保证操作的原子性，这个时候<strong>只能用锁来保证原子性</strong></li></ul></li><li>引出来 ABA 问题<ul><li>当进行获取主内存值时，该内存值在写入主内存时已经被修改了 N 次，但是最终又改成原来的值。即其他线程先把 A 改成 B 又改回 A，主线程<strong>仅能判断出共享变量的值与最初值 A 是否相同</strong>，不能感知到这种从 A 改为 B 又 改回 A 的情况，这时 CAS 虽然成功，但是过程存在问题。</li></ul></li></ul><h3 id="乐观锁"><a href="#乐观锁" class="headerlink" title="乐观锁"></a>乐观锁</h3><p>CAS 与 synchronized 总结：</p><ul><li>synchronized 是从悲观的角度出发：总是假设最坏的情况，每次去拿数据的时候都认为别人会修改，所以每次在拿数据的时候都会上锁，这样别人想拿这个数据就会阻塞（共享资源每次只给一个线程使用，其它线程阻塞，用完后再把资源转让给其它线程），因此 synchronized 也称之为悲观锁，ReentrantLock 也是一种悲观锁，性能较差</li><li>CAS 是从乐观的角度出发：总是假设最好的情况，每次去拿数据的时候都认为别人不会修改，所以不会上锁，但是在更新的时候会判断一下在此期间别人有没有去更新这个数据。<strong>如果别人修改过，则获取现在最新的值，如果别人没修改过，直接修改共享数据的值</strong>，CAS 这种机制也称之为乐观锁，综合性能较好</li></ul><h3 id="案例"><a href="#案例" class="headerlink" title="案例"></a>案例</h3><p>如何 保证 <code>account.withdraw</code> 取款方法的线程安全</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">interface</span> <span class="token class-name">Account</span> <span class="token punctuation">{</span>  <span class="token comment" spellcheck="true">// 获取余额</span>  Integer <span class="token function">getBalance</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment" spellcheck="true">// 取款</span>  <span class="token keyword">void</span> <span class="token function">withdraw</span><span class="token punctuation">(</span>Integer amount<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token keyword">class</span> <span class="token class-name">AccountUnsafe</span> <span class="token keyword">implements</span> <span class="token class-name">Account</span><span class="token punctuation">{</span>    <span class="token keyword">private</span> Integer balance<span class="token punctuation">;</span>    <span class="token keyword">public</span> <span class="token function">AccountUnsafe</span><span class="token punctuation">(</span>Integer balance<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">this</span><span class="token punctuation">.</span>balance <span class="token operator">=</span> balance<span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> Integer <span class="token function">getBalance</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">return</span> balance<span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">withdraw</span><span class="token punctuation">(</span>Integer amount<span class="token punctuation">)</span> <span class="token punctuation">{</span>        balance <span class="token operator">-=</span> amount<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="解决方法1：加锁"><a href="#解决方法1：加锁" class="headerlink" title="解决方法1：加锁"></a>解决方法1：加锁</h4><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">AccountUnsafe</span> <span class="token keyword">implements</span> <span class="token class-name">Account</span> <span class="token punctuation">{</span>    <span class="token keyword">private</span> Integer balance<span class="token punctuation">;</span>      <span class="token keyword">public</span> <span class="token function">AccountUnsafe</span><span class="token punctuation">(</span>Integer balance<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token keyword">this</span><span class="token punctuation">.</span>balance <span class="token operator">=</span> balance<span class="token punctuation">;</span>    <span class="token punctuation">}</span>      <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> <span class="token keyword">synchronized</span> Integer <span class="token function">getBalance</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token keyword">return</span> balance<span class="token punctuation">;</span>    <span class="token punctuation">}</span>      <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> <span class="token keyword">synchronized</span> <span class="token keyword">void</span> <span class="token function">withdraw</span><span class="token punctuation">(</span>Integer amount<span class="token punctuation">)</span> <span class="token punctuation">{</span>            balance <span class="token operator">-=</span> amount<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="解决方法2：无锁"><a href="#解决方法2：无锁" class="headerlink" title="解决方法2：无锁"></a>解决方法2：无锁</h4><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">AccountSafe</span> <span class="token keyword">implements</span> <span class="token class-name">Account</span> <span class="token punctuation">{</span>        <span class="token keyword">private</span> AtomicInteger balance<span class="token punctuation">;</span>          <span class="token keyword">public</span> <span class="token function">AccountSafe</span><span class="token punctuation">(</span>Integer balance<span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token keyword">this</span><span class="token punctuation">.</span>balance <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">AtomicInteger</span><span class="token punctuation">(</span>balance<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>      <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> Integer <span class="token function">getBalance</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token keyword">return</span> balance<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>      <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">withdraw</span><span class="token punctuation">(</span>Integer amount<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 需要不断尝试，直到成功为止</span>        <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">//获取余额最新值</span>            <span class="token keyword">int</span> prev <span class="token operator">=</span> balance<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 比如拿到了旧值 1000</span>            <span class="token comment" spellcheck="true">//要修改的余额</span>            <span class="token keyword">int</span> next <span class="token operator">=</span> prev <span class="token operator">-</span> amount<span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 在这个基础上 1000-10 = 990</span>            <span class="token comment" spellcheck="true">//真正修改</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span>balance<span class="token punctuation">.</span><span class="token function">compareAndSet</span><span class="token punctuation">(</span>prev<span class="token punctuation">,</span> next<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                    <span class="token keyword">break</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>             <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 可以简化为下面的方法</span>        <span class="token comment" spellcheck="true">// balance.addAndGet(-1 * amount);</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span>         <span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>其中的关键是 compareAndSet，它的简称就是 CAS （也有 Compare And Swap 的说法），它必须是原子操作。</p><p>compareAndSet：在 set 前，先比较 prev 与当前值(balance)</p><ul><li>不一致了，next 作废，返回 false 表示失败<ul><li><p>比如别的线程已经做了减法，当前值已经被减成了 990，那么本线程的这次 990 就作废了，进入 while 下次循环重试</p></li><li><p>一致，以 next 设置为新值，返回 true 表示成功</p></li></ul></li></ul><p><img src="https://img.jwt1399.top/img/202212302049142.png"></p><h2 id="❷Atomic"><a href="#❷Atomic" class="headerlink" title="❷Atomic"></a>❷Atomic</h2><h3 id="原子整数"><a href="#原子整数" class="headerlink" title="原子整数"></a>原子整数</h3><p>常见原子类：AtomicInteger、AtomicBoolean、AtomicLong</p><p>构造方法：</p><ul><li><code>public AtomicInteger()</code>：初始化一个默认值为 0 的原子型 Integer</li><li><code>public AtomicInteger(int initialValue)</code>：初始化一个指定值的原子型 Integer</li></ul><p>常用API：</p><table><thead><tr><th>方法</th><th>作用</th></tr></thead><tbody><tr><td>public final int get()</td><td>获取 AtomicInteger 的值</td></tr><tr><td>public final int getAndIncrement()</td><td>以原子方式将当前值加 1，返回的是自增前的值（i++）</td></tr><tr><td>public final int incrementAndGet()</td><td>以原子方式将当前值加 1，返回的是自增后的值（++i）</td></tr><tr><td>public final int getAndSet(int value)</td><td>以原子方式设置为 newValue 的值，返回旧值</td></tr><tr><td>public final int addAndGet(int data)</td><td>以原子方式将输入的数值与实例中的值相加并返回</td></tr></tbody></table><pre class="line-numbers language-java"><code class="language-java">AtomicInteger i <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">AtomicInteger</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 获取并自增（i = 0, 结果 i = 1, 返回 0），类似于 i++</span>System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>i<span class="token punctuation">.</span><span class="token function">getAndIncrement</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 自增并获取（i = 1, 结果 i = 2, 返回 2），类似于 ++i</span>System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>i<span class="token punctuation">.</span><span class="token function">incrementAndGet</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 自减并获取（i = 2, 结果 i = 1, 返回 1），类似于 --i</span>System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>i<span class="token punctuation">.</span><span class="token function">decrementAndGet</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 获取并自减（i = 1, 结果 i = 0, 返回 1），类似于 i--</span>System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>i<span class="token punctuation">.</span><span class="token function">getAndDecrement</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 获取并加值（i = 0, 结果 i = 5, 返回 0）</span>System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>i<span class="token punctuation">.</span><span class="token function">getAndAdd</span><span class="token punctuation">(</span><span class="token number">5</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 加值并获取（i = 5, 结果 i = 0, 返回 0）</span>System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>i<span class="token punctuation">.</span><span class="token function">addAndGet</span><span class="token punctuation">(</span><span class="token operator">-</span><span class="token number">5</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 获取并更新（i = 0, p 为 i 的当前值, 结果 i = -2, 返回 0）</span><span class="token comment" spellcheck="true">// 其中函数中的操作能保证原子，但函数需要无副作用</span>System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>i<span class="token punctuation">.</span><span class="token function">getAndUpdate</span><span class="token punctuation">(</span>p <span class="token operator">-</span><span class="token operator">></span> p <span class="token operator">-</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 更新并获取（i = -2, p 为 i 的当前值, 结果 i = 0, 返回 0）</span><span class="token comment" spellcheck="true">// 其中函数中的操作能保证原子，但函数需要无副作用</span>System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>i<span class="token punctuation">.</span><span class="token function">updateAndGet</span><span class="token punctuation">(</span>p <span class="token operator">-</span><span class="token operator">></span> p <span class="token operator">+</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 获取并计算（i = 0, p 为 i 的当前值, x 为参数1, 结果 i = 10, 返回 0）</span><span class="token comment" spellcheck="true">// 其中函数中的操作能保证原子，但函数需要无副作用</span><span class="token comment" spellcheck="true">// getAndUpdate 如果在 lambda 中引用了外部的局部变量，要保证该局部变量是 final 的</span><span class="token comment" spellcheck="true">// getAndAccumulate 可以通过 参数1 来引用外部的局部变量，但因为其不在 lambda 中因此不必是 final</span>System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>i<span class="token punctuation">.</span><span class="token function">getAndAccumulate</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">,</span> <span class="token punctuation">(</span>p<span class="token punctuation">,</span> x<span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> p <span class="token operator">+</span> x<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 计算并获取（i = 10, p 为 i 的当前值, x 为参数1, 结果 i = 0, 返回 0）</span><span class="token comment" spellcheck="true">// 其中函数中的操作能保证原子，但函数需要无副作用</span>System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>i<span class="token punctuation">.</span><span class="token function">accumulateAndGet</span><span class="token punctuation">(</span><span class="token operator">-</span><span class="token number">10</span><span class="token punctuation">,</span> <span class="token punctuation">(</span>p<span class="token punctuation">,</span> x<span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> p <span class="token operator">+</span> x<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="原理分析"><a href="#原理分析" class="headerlink" title="原理分析"></a>原理分析</h3><p><strong>AtomicInteger 原理</strong>：自旋锁  + CAS 算法</p><p>CAS 算法：有 3 个操作数（内存值 V， 旧的预期值 A，要修改的值 B）</p><ul><li>当旧的预期值 A &#x3D;&#x3D; 内存值 V   此时可以修改，将 V 改为 B</li><li>当旧的预期值 A !&#x3D;  内存值 V   此时不能修改，并重新获取现在的最新值，重新获取的动作就是自旋</li></ul><p>分析 getAndSet 方法：</p><ul><li><p>AtomicInteger：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token keyword">int</span> <span class="token function">getAndSet</span><span class="token punctuation">(</span><span class="token keyword">int</span> newValue<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">/**    * this: 当前对象    * valueOffset:内存偏移量，内存地址    */</span>    <span class="token keyword">return</span> unsafe<span class="token punctuation">.</span><span class="token function">getAndSetInt</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> valueOffset<span class="token punctuation">,</span> newValue<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>valueOffset：偏移量表示该变量值相对于当前对象地址的偏移，Unsafe 就是根据内存偏移地址获取数据</p><pre class="line-numbers language-java"><code class="language-java">valueOffset <span class="token operator">=</span> unsafe<span class="token punctuation">.</span><span class="token function">objectFieldOffset</span>                <span class="token punctuation">(</span>AtomicInteger<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">.</span><span class="token function">getDeclaredField</span><span class="token punctuation">(</span><span class="token string">"value"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//调用本地方法   --></span><span class="token keyword">public</span> <span class="token keyword">native</span> <span class="token keyword">long</span> <span class="token function">objectFieldOffset</span><span class="token punctuation">(</span>Field var1<span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>unsafe 类：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// val1: AtomicInteger对象本身，var2: 该对象值得引用地址，var4: 需要变动的数</span><span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token keyword">int</span> <span class="token function">getAndSetInt</span><span class="token punctuation">(</span>Object var1<span class="token punctuation">,</span> <span class="token keyword">long</span> var2<span class="token punctuation">,</span> <span class="token keyword">int</span> var4<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">int</span> var5<span class="token punctuation">;</span>    <span class="token keyword">do</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// var5: 用 var1 和 var2 找到的内存中的真实值</span>        var5 <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">getIntVolatile</span><span class="token punctuation">(</span>var1<span class="token punctuation">,</span> var2<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span> <span class="token keyword">while</span><span class="token punctuation">(</span><span class="token operator">!</span><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">compareAndSwapInt</span><span class="token punctuation">(</span>var1<span class="token punctuation">,</span> var2<span class="token punctuation">,</span> var5<span class="token punctuation">,</span> var4<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> var5<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>var5：从主内存中拷贝到工作内存中的值（每次都要从主内存拿到最新的值到本地内存），然后执行 <code>compareAndSwapInt()</code> 再和主内存的值进行比较，假设方法返回 false，那么就一直执行 while 方法，直到期望的值和真实值一样，修改数据</p></li><li><p>变量 value 用 volatile 修饰，保证了多线程之间的内存可见性，避免线程从工作缓存中获取失效的变量</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">volatile</span> <span class="token keyword">int</span> value<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p><strong>CAS 必须借助 volatile 才能读取到共享变量的最新值来实现比较并交换的效果</strong></p></li></ul><p>分析 getAndUpdate 方法：</p><ul><li><p>getAndUpdate：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token keyword">int</span> <span class="token function">getAndUpdate</span><span class="token punctuation">(</span>IntUnaryOperator updateFunction<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">int</span> prev<span class="token punctuation">,</span> next<span class="token punctuation">;</span>    <span class="token keyword">do</span> <span class="token punctuation">{</span>        prev <span class="token operator">=</span> <span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//当前值，cas的期望值</span>        next <span class="token operator">=</span> updateFunction<span class="token punctuation">.</span><span class="token function">applyAsInt</span><span class="token punctuation">(</span>prev<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//期望值更新到该值</span>    <span class="token punctuation">}</span> <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">compareAndSet</span><span class="token punctuation">(</span>prev<span class="token punctuation">,</span> next<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//自旋</span>    <span class="token keyword">return</span> prev<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>函数式接口：可以自定义操作逻辑</p><pre class="line-numbers language-java"><code class="language-java">AtomicInteger a <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">AtomicInteger</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>a<span class="token punctuation">.</span><span class="token function">getAndUpdate</span><span class="token punctuation">(</span>i <span class="token operator">-</span><span class="token operator">></span> i <span class="token operator">+</span> <span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre></li><li><p>compareAndSet：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token keyword">boolean</span> <span class="token function">compareAndSet</span><span class="token punctuation">(</span><span class="token keyword">int</span> expect<span class="token punctuation">,</span> <span class="token keyword">int</span> update<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">/**    * this: 当前对象    * valueOffset:内存偏移量，内存地址    * expect:期望的值    * update: 更新的值    */</span>    <span class="token keyword">return</span> unsafe<span class="token punctuation">.</span><span class="token function">compareAndSwapInt</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> valueOffset<span class="token punctuation">,</span> expect<span class="token punctuation">,</span> update<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li></ul><h3 id="原子引用"><a href="#原子引用" class="headerlink" title="原子引用"></a>原子引用</h3><p>原子引用：对 Object 进行原子操作，提供一种读和写都是原子性的对象引用变量</p><p>原子引用类：AtomicReference、AtomicStampedReference、AtomicMarkableReference</p><p>AtomicReference 类：</p><ul><li><p>构造方法：<code>AtomicReference&lt;T&gt; atomicReference = new AtomicReference&lt;T&gt;()</code></p></li><li><p>常用 API：</p><ul><li><code>public final boolean compareAndSet(V expectedValue, V newValue)</code>：CAS 操作</li><li><code>public final void set(V newValue)</code>：将值设置为 newValue </li><li><code>public final V get()</code>：返回当前值</li></ul></li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">AtomicReferenceDemo</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>        Student s1 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Student</span><span class="token punctuation">(</span><span class="token number">33</span><span class="token punctuation">,</span> <span class="token string">"z3"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token comment" spellcheck="true">// 创建原子引用包装类</span>        AtomicReference<span class="token operator">&lt;</span>Student<span class="token operator">></span> atomicReference <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">AtomicReference</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 设置主内存共享变量为s1</span>        atomicReference<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span>s1<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 比较并交换，如果现在主物理内存的值为 z3，那么交换成 l4</span>        <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            Student s2 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Student</span><span class="token punctuation">(</span><span class="token number">44</span><span class="token punctuation">,</span> <span class="token string">"l4"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span>atomicReference<span class="token punctuation">.</span><span class="token function">compareAndSet</span><span class="token punctuation">(</span>s1<span class="token punctuation">,</span> s2<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token keyword">break</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>atomicReference<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token keyword">class</span> <span class="token class-name">Student</span> <span class="token punctuation">{</span>    <span class="token keyword">private</span> <span class="token keyword">int</span> id<span class="token punctuation">;</span>    <span class="token keyword">private</span> String name<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">//。。。。</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="原子数组"><a href="#原子数组" class="headerlink" title="原子数组"></a>原子数组</h3><p>原子数组类：AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray</p><p>AtomicIntegerArray 类方法：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">/***   ithe index* expect the expected value* update the new value*/</span><span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token keyword">boolean</span> <span class="token function">compareAndSet</span><span class="token punctuation">(</span><span class="token keyword">int</span> i<span class="token punctuation">,</span> <span class="token keyword">int</span> expect<span class="token punctuation">,</span> <span class="token keyword">int</span> update<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">return</span> <span class="token function">compareAndSetRaw</span><span class="token punctuation">(</span><span class="token function">checkedByteOffset</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span><span class="token punctuation">,</span> expect<span class="token punctuation">,</span> update<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="原子更新器"><a href="#原子更新器" class="headerlink" title="原子更新器"></a>原子更新器</h3><p>原子更新器类：AtomicReferenceFieldUpdater、AtomicIntegerFieldUpdater、AtomicLongFieldUpdater</p><p>利用字段更新器，可以针对对象的某个域（Field）进行原子操作，只能配合 volatile 修饰的字段使用，否则会出现异常 <code>IllegalArgumentException: Must be volatile type</code></p><p>常用 API：</p><ul><li><code>static &lt;U&gt; AtomicIntegerFieldUpdater&lt;U&gt; newUpdater(Class&lt;U&gt; c, String fieldName)</code>：构造方法</li><li><code>abstract boolean compareAndSet(T obj, int expect, int update)</code>：CAS</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">UpdateDemo</span> <span class="token punctuation">{</span>    <span class="token keyword">private</span> <span class="token keyword">volatile</span> <span class="token keyword">int</span> field<span class="token punctuation">;</span>        <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>        AtomicIntegerFieldUpdater fieldUpdater <span class="token operator">=</span> AtomicIntegerFieldUpdater                    <span class="token punctuation">.</span><span class="token function">newUpdater</span><span class="token punctuation">(</span>UpdateDemo<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">,</span> <span class="token string">"field"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        UpdateDemo updateDemo <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">UpdateDemo</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        fieldUpdater<span class="token punctuation">.</span><span class="token function">compareAndSet</span><span class="token punctuation">(</span>updateDemo<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>updateDemo<span class="token punctuation">.</span>field<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//10</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="原子累加器"><a href="#原子累加器" class="headerlink" title="原子累加器"></a>原子累加器</h3><p>原子累加器类：LongAdder、DoubleAdder、LongAccumulator、DoubleAccumulator </p><p>LongAdder 和 LongAccumulator 区别：</p><p>相同点：</p><ul><li>LongAddr 与 LongAccumulator 类都是使用非阻塞算法 CAS 实现的</li><li>LongAddr 类是 LongAccumulator 类的一个特例，只是 LongAccumulator 提供了更强大的功能，可以自定义累加规则，当accumulatorFunction 为 null 时就等价于 LongAddr</li></ul><p>不同点：</p><ul><li><p>调用 casBase 时，LongAccumulator 使用 function.applyAsLong(b &#x3D; base, x) 来计算，LongAddr 使用 casBase(b &#x3D; base, b + x) </p></li><li><p>LongAccumulator 类功能更加强大，构造方法参数中</p><ul><li>accumulatorFunction 是一个双目运算器接口，可以指定累加规则，比如累加或者相乘，其根据输入的两个参数返回一个计算值，LongAdder 内置累加规则</li><li>identity 则是 LongAccumulator 累加器的初始值，LongAccumulator 可以为累加器提供非0的初始值，而 LongAdder 只能提供默认的 0</li></ul></li></ul><h2 id="❸Adder"><a href="#❸Adder" class="headerlink" title="❸Adder"></a>❸Adder</h2><p>LongAdder  是 Java8 提供的类， 跟 AtomicLong 有相同的效果，但对 CAS 机制进行了优化，尝试使用<strong>分段 CAS</strong> 以及<strong>自动分段迁移</strong>的方式来大幅度提升多线程高并发执行 CAS 操作的性能</p><p>CAS 底层实现是在一个循环中不断地尝试修改目标值，直到修改成功。如果竞争不激烈修改成功率很高，否则失败率很高，失败后这些重复的原子性操作会耗费性能（导致大量线程<strong>空循环，自旋转</strong>）</p><p>优化核心思想：数据分离，将 AtomicLong 的<strong>单点的更新压力分担到各个节点，空间换时间</strong>，在低并发的时候直接更新，可以保障和 AtomicLong 的性能基本一致，而在高并发的时候通过分散减少竞争，提高了性能</p><p><strong>分段 CAS 机制</strong>：</p><ul><li>在发生竞争时，创建 Cell 数组用于将不同线程的操作离散（通过 hash 等算法映射）到不同的节点上</li><li>设置多个累加单元（会根据需要扩容，最大为 CPU 核数），Therad-0 累加 Cell[0]，而 Thread-1 累加 Cell[1] 等，最后将结果汇总</li><li>在累加时操作的不同的 Cell 变量，因此减少了 CAS 重试失败，从而提高性能</li></ul><p><strong>自动分段迁移机制</strong>：某个 Cell 的 value 执行 CAS 失败，就会自动寻找另一个 Cell 分段内的 value 值进行 CAS 操作</p><h2 id="❹Unsafe"><a href="#❹Unsafe" class="headerlink" title="❹Unsafe"></a>❹Unsafe</h2><p>Unsafe 是 CAS 的核心类，由于 Java 无法直接访问底层系统，需要通过本地（Native）方法来访问</p><p>Unsafe 类存在 sun.misc 包，其中所有方法都是 native 修饰的，都是直接调用<strong>操作系统底层资源</strong>执行相应的任务，基于该类可以直接操作特定的内存数据，其内部方法操作类似 C 的指针</p><h2 id="❺final"><a href="#❺final" class="headerlink" title="❺final"></a>❺final</h2><h3 id="原理-2"><a href="#原理-2" class="headerlink" title="原理"></a>原理</h3><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">TestFinal</span> <span class="token punctuation">{</span>    <span class="token keyword">final</span> <span class="token keyword">int</span> a <span class="token operator">=</span> <span class="token number">20</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>字节码：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token number">0</span><span class="token operator">:</span> aload_0<span class="token number">1</span><span class="token operator">:</span> invokespecial #<span class="token number">1</span> <span class="token comment" spellcheck="true">// Method java/lang/Object."&lt;init>":()V</span><span class="token number">4</span><span class="token operator">:</span> aload_0<span class="token number">5</span><span class="token operator">:</span> bipush <span class="token number">20</span><span class="token comment" spellcheck="true">// 将值直接放入栈中</span><span class="token number">7</span><span class="token operator">:</span> putfield #<span class="token number">2</span> <span class="token comment" spellcheck="true">// Field a:I</span><span class="token operator">&lt;</span><span class="token operator">--</span> 写屏障<span class="token number">10</span><span class="token operator">:</span> <span class="token keyword">return</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>final 变量的赋值通过 putfield 指令来完成，在这条指令之后也会加入写屏障，保证在其它线程读到它的值时不会出现为 0 的情况</p><p>其他线程访问 final 修饰的变量<strong>会复制一份放入栈中</strong>，效率更高</p><h3 id="不可变"><a href="#不可变" class="headerlink" title="不可变"></a>不可变</h3><p>不可变：如果一个对象不能够修改其内部状态（属性），那么就是不可变对象</p><p>不可变对象线程安全的，不存在并发修改和可见性问题，是另一种避免竞争的方式</p><p>String 类也是不可变的，该类和类中所有属性都是 final 的</p><ul><li><p>类用 final 修饰保证了该类中的方法不能被覆盖，防止子类无意间破坏不可变性</p></li><li><p>无写入方法（set）确保外部不能对内部属性进行修改</p></li><li><p>属性用 final 修饰保证了该属性是只读的，不能修改</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token keyword">class</span> <span class="token class-name">String</span>    <span class="token keyword">implements</span> <span class="token class-name">java<span class="token punctuation">.</span>io<span class="token punctuation">.</span>Serializable</span><span class="token punctuation">,</span> Comparable<span class="token operator">&lt;</span>String<span class="token operator">></span><span class="token punctuation">,</span> CharSequence <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">/** The value is used for character storage. */</span>    <span class="token keyword">private</span> <span class="token keyword">final</span> <span class="token keyword">char</span> value<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">//....</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>更改 String 类数据时，会构造新字符串对象，生成新的 char[] value，通过<strong>创建副本对象来避免共享的方式称之为保护性拷贝</strong></p></li></ul><h2 id="❻ThreadLocal"><a href="#❻ThreadLocal" class="headerlink" title="❻ThreadLocal"></a>❻ThreadLocal</h2><h3 id="基本介绍"><a href="#基本介绍" class="headerlink" title="基本介绍"></a>基本介绍</h3><p>ThreadLocal 类用来提供线程内部的局部变量，这种变量在多线程环境下访问（通过 get 和 set 方法访问）时能保证各个线程的变量相对独立于其他线程内的变量，分配在堆内的 <strong>TLAB</strong> 中</p><p>ThreadLocal 实例通常来说都是 <code>private static</code> 类型的，属于一个线程的本地变量，用于关联线程和线程上下文。每个线程都会在 ThreadLocal 中保存一份该线程独有的数据，所以是线程安全的</p><p>ThreadLocal 作用：</p><ul><li><p>线程并发：应用在多线程并发的场景下</p></li><li><p>传递数据：通过 ThreadLocal 实现在同一线程不同函数或组件中传递公共变量，减少传递复杂度</p></li><li><p>线程隔离：每个线程的变量都是独立的，不会互相影响</p></li></ul><p>对比 synchronized：</p><table><thead><tr><th></th><th>synchronized</th><th>ThreadLocal</th></tr></thead><tbody><tr><td>原理</td><td>同步机制采用<strong>以时间换空间</strong>的方式，只提供了一份变量，让不同的线程排队访问</td><td>ThreadLocal 采用<strong>以空间换时间</strong>的方式，为每个线程都提供了一份变量的副本，从而实现同时访问而相不干扰</td></tr><tr><td>侧重点</td><td>多个线程之间访问资源的同步</td><td>多线程中让每个线程之间的数据相互隔离</td></tr></tbody></table><h3 id="基本使用-5"><a href="#基本使用-5" class="headerlink" title="基本使用"></a>基本使用</h3><h4 id="常用方法"><a href="#常用方法" class="headerlink" title="常用方法"></a>常用方法</h4><table><thead><tr><th>方法</th><th>描述</th></tr></thead><tbody><tr><td>ThreadLocal&lt;&gt;()</td><td>创建 ThreadLocal 对象</td></tr><tr><td>protected T initialValue()</td><td>返回当前线程局部变量的初始值</td></tr><tr><td>public void set( T value)</td><td>设置当前线程绑定的局部变量</td></tr><tr><td>public T get()</td><td>获取当前线程绑定的局部变量</td></tr><tr><td>public void remove()</td><td>移除当前线程绑定的局部变量</td></tr></tbody></table><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">MyDemo</span> <span class="token punctuation">{</span>    <span class="token keyword">private</span> <span class="token keyword">static</span> ThreadLocal<span class="token operator">&lt;</span>String<span class="token operator">></span> tl <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ThreadLocal</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">private</span> String content<span class="token punctuation">;</span>    <span class="token keyword">private</span> String <span class="token function">getContent</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 获取当前线程绑定的变量</span>        <span class="token keyword">return</span> tl<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">setContent</span><span class="token punctuation">(</span>String content<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 变量content绑定到当前线程</span>        tl<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span>content<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>        MyDemo demo <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">MyDemo</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> <span class="token number">5</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            Thread thread <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Runnable</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token annotation punctuation">@Override</span>                <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">run</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                    <span class="token comment" spellcheck="true">// 设置数据</span>                    demo<span class="token punctuation">.</span><span class="token function">setContent</span><span class="token punctuation">(</span>Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">"的数据"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                    System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"-----------------------"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                    System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">"--->"</span> <span class="token operator">+</span> demo<span class="token punctuation">.</span><span class="token function">getContent</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token punctuation">}</span>            <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            thread<span class="token punctuation">.</span><span class="token function">setName</span><span class="token punctuation">(</span><span class="token string">"线程"</span> <span class="token operator">+</span> i<span class="token punctuation">)</span><span class="token punctuation">;</span>            thread<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">/**-----------------------线程0--->线程0的数据-----------------------线程1--->线程1的数据-----------------------线程2--->线程2的数据-----------------------线程3--->线程3的数据-----------------------线程4--->线程4的数据**/</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="应用场景"><a href="#应用场景" class="headerlink" title="应用场景"></a>应用场景</h4><p>ThreadLocal 适用于下面两种场景：</p><ul><li>每个线程需要有自己单独的实例</li><li>实例需要在多个方法中共享，但不希望被多线程共享</li></ul><p>ThreadLocal 方案有两个突出的优势： </p><ol><li>传递数据：保存每个线程绑定的数据，在需要的地方可以直接获取，避免参数直接传递带来的代码耦合问题</li><li>线程隔离：各线程之间的数据相互隔离却又具备并发性，避免同步方式带来的性能损失</li></ol><p>ThreadLocal 用于数据连接的事务管理：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">JdbcUtils</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// ThreadLocal对象，将connection绑定在当前线程中</span>    <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> ThreadLocal<span class="token operator">&lt;</span>Connection<span class="token operator">></span> tl <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ThreadLocal</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// c3p0 数据库连接池对象属性</span>    <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> ComboPooledDataSource ds <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ComboPooledDataSource</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 获取连接</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> Connection <span class="token function">getConnection</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">throws</span> SQLException <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">//取出当前线程绑定的connection对象</span>        Connection conn <span class="token operator">=</span> tl<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>conn <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">//如果没有，则从连接池中取出</span>            conn <span class="token operator">=</span> ds<span class="token punctuation">.</span><span class="token function">getConnection</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">//再将connection对象绑定到当前线程中，非常重要的操作</span>            tl<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span>conn<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> conn<span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">// ...</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>用 ThreadLocal 使 SimpleDateFormat 从独享变量变成单个线程变量：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ThreadLocalDateUtil</span> <span class="token punctuation">{</span>    <span class="token keyword">private</span> <span class="token keyword">static</span> ThreadLocal<span class="token operator">&lt;</span>DateFormat<span class="token operator">></span> threadLocal <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ThreadLocal</span><span class="token operator">&lt;</span>DateFormat<span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token annotation punctuation">@Override</span>        <span class="token keyword">protected</span> DateFormat <span class="token function">initialValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">SimpleDateFormat</span><span class="token punctuation">(</span><span class="token string">"yyyy-MM-dd HH:mm:ss"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span><span class="token punctuation">;</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> Date <span class="token function">parse</span><span class="token punctuation">(</span>String dateStr<span class="token punctuation">)</span> <span class="token keyword">throws</span> ParseException <span class="token punctuation">{</span>        <span class="token keyword">return</span> threadLocal<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">parse</span><span class="token punctuation">(</span>dateStr<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> String <span class="token function">format</span><span class="token punctuation">(</span>Date date<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">return</span> threadLocal<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">format</span><span class="token punctuation">(</span>date<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="实现原理-3"><a href="#实现原理-3" class="headerlink" title="实现原理"></a>实现原理</h3><h4 id="底层结构"><a href="#底层结构" class="headerlink" title="底层结构"></a>底层结构</h4><p>JDK8 以前：每个 ThreadLocal 都创建一个 Map，然后用线程作为 Map 的 key，要存储的局部变量作为 Map 的 value，达到各个线程的局部变量隔离的效果。这种结构会造成 Map 结构过大和内存泄露，因为 Thread 停止后无法通过 key 删除对应的数据</p><p><img src="https://img.jwt1399.top/img/202301032103166.png"></p><p>JDK8 以后：每个 Thread 维护一个 ThreadLocalMap，这个 Map 的 key 是 ThreadLocal 实例本身，value 是真正要存储的值</p><ul><li><strong>每个 Thread 线程内部都有一个 Map (ThreadLocalMap)</strong></li><li>Map 里面存储 ThreadLocal 对象（key）和线程的私有变量（value）</li><li>Thread 内部的 Map 是由 ThreadLocal 维护的，由 ThreadLocal 负责向 map 获取和设置线程的变量值</li><li>对于不同的线程，每次获取副本值时，别的线程并不能获取到当前线程的副本值，形成副本的隔离，互不干扰</li></ul><p><img src="https://img.jwt1399.top/img/202301032103152.png"></p><p>JDK8 前后对比：</p><ul><li>每个 Map 存储的 Entry 数量会变少，因为之前的存储数量由 Thread 的数量决定，现在由 ThreadLocal 的数量决定，在实际编程当中，往往 ThreadLocal 的数量要少于 Thread 的数量</li><li>当 Thread 销毁之后，对应的 ThreadLocalMap 也会随之销毁，能减少内存的使用，<strong>防止内存泄露</strong></li></ul><hr><h4 id="成员变量"><a href="#成员变量" class="headerlink" title="成员变量"></a>成员变量</h4><ul><li><p>Thread 类的相关属性：<strong>每一个线程持有一个 ThreadLocalMap 对象</strong>，存放由 ThreadLocal 和数据组成的 Entry 键值对</p><pre class="line-numbers language-java"><code class="language-java">ThreadLocal<span class="token punctuation">.</span>ThreadLocalMap threadLocals <span class="token operator">=</span> null<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p><img src="https://img.jwt1399.top/img/202301032111440.png"></p></li><li><p>计算 ThreadLocal 对象的哈希值：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">final</span> <span class="token keyword">int</span> threadLocalHashCode <span class="token operator">=</span> <span class="token function">nextHashCode</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>使用 <code>threadLocalHashCode &amp; (table.length - 1)</code> 计算当前 entry 需要存放的位置</p></li><li><p>每创建一个 ThreadLocal 对象就会使用 nextHashCode 分配一个 hash 值给这个对象：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">static</span> AtomicInteger nextHashCode <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">AtomicInteger</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre></li><li><p>斐波那契数也叫黄金分割数，hash 的<strong>增量</strong>就是这个数字，带来的好处是 hash 分布非常均匀：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> HASH_INCREMENT <span class="token operator">=</span> <span class="token number">0x61c88647</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre></li></ul><h4 id="成员方法"><a href="#成员方法" class="headerlink" title="成员方法"></a>成员方法</h4><p>方法都是线程安全的，因为 ThreadLocal 属于一个线程的，ThreadLocal 中的方法，逻辑都是获取当前线程维护的 ThreadLocalMap 对象，然后进行数据的增删改查，没有指定初始值的 threadlcoal 对象默认赋值为 null</p><ul><li><p><code>initialValue()</code>：返回该线程局部变量的初始值</p><ul><li>延迟调用的方法，在执行 get 方法时才执行</li><li>该方法缺省（默认）实现直接返回一个 null</li><li>如果想要一个初始值，可以重写此方法， 该方法是一个 <code>protected</code> 的方法，为了让子类覆盖而设计的</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">protected</span> T <span class="token function">initialValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">return</span> null<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre></li><li><p><code>nextHashCode()</code>：计算哈希值，ThreadLocal 的散列方式称之为<strong>斐波那契散列</strong>，每次获取哈希值都会加上 HASH_INCREMENT，这样做可以尽量避免 hash 冲突，让哈希值能均匀的分布在 2 的 n 次方的数组中</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">int</span> <span class="token function">nextHashCode</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 哈希值自增一个 HASH_INCREMENT 数值</span>    <span class="token keyword">return</span> nextHashCode<span class="token punctuation">.</span><span class="token function">getAndAdd</span><span class="token punctuation">(</span>HASH_INCREMENT<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre></li><li><p><code>set()</code>：修改当前线程与当前 threadlocal 对象相关联的线程局部变量</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">set</span><span class="token punctuation">(</span>T value<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 获取当前线程对象</span>    Thread t <span class="token operator">=</span> Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 获取此线程对象中维护的 ThreadLocalMap 对象</span>    ThreadLocalMap map <span class="token operator">=</span> <span class="token function">getMap</span><span class="token punctuation">(</span>t<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 判断 map 是否存在</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>map <span class="token operator">!=</span> null<span class="token punctuation">)</span>        <span class="token comment" spellcheck="true">// 调用 threadLocalMap.set 方法进行重写或者添加</span>        map<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> value<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">else</span>        <span class="token comment" spellcheck="true">// map 为空，调用 createMap 进行 ThreadLocalMap 对象的初始化。参数1是当前线程，参数2是局部变量</span>        <span class="token function">createMap</span><span class="token punctuation">(</span>t<span class="token punctuation">,</span> value<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// 获取当前线程 Thread 对应维护的 ThreadLocalMap </span>ThreadLocalMap <span class="token function">getMap</span><span class="token punctuation">(</span>Thread t<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">return</span> t<span class="token punctuation">.</span>threadLocals<span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">// 创建当前线程Thread对应维护的ThreadLocalMap </span><span class="token keyword">void</span> <span class="token function">createMap</span><span class="token punctuation">(</span>Thread t<span class="token punctuation">,</span> T firstValue<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 【这里的 this 是调用此方法的 threadLocal】，创建一个新的 Map 并设置第一个数据</span>    t<span class="token punctuation">.</span>threadLocals <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ThreadLocalMap</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> firstValue<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p><code>get()</code>：获取当前线程与当前 ThreadLocal 对象相关联的线程局部变量</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> T <span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    Thread t <span class="token operator">=</span> Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    ThreadLocalMap map <span class="token operator">=</span> <span class="token function">getMap</span><span class="token punctuation">(</span>t<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 如果此map存在</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>map <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 以当前的 ThreadLocal 为 key，调用 getEntry 获取对应的存储实体 e</span>        ThreadLocalMap<span class="token punctuation">.</span>Entry e <span class="token operator">=</span> map<span class="token punctuation">.</span><span class="token function">getEntry</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 对 e 进行判空 </span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>e <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 获取存储实体 e 对应的 value值</span>            T result <span class="token operator">=</span> <span class="token punctuation">(</span>T<span class="token punctuation">)</span>e<span class="token punctuation">.</span>value<span class="token punctuation">;</span>            <span class="token keyword">return</span> result<span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">/*有两种情况有执行当前代码      第一种情况: map 不存在，表示此线程没有维护的 ThreadLocalMap 对象      第二种情况: map 存在, 但是【没有与当前 ThreadLocal 关联的 entry】，就会设置为默认值 */</span>    <span class="token comment" spellcheck="true">// 初始化当前线程与当前 threadLocal 对象相关联的 value</span>    <span class="token keyword">return</span> <span class="token function">setInitialValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> T <span class="token function">setInitialValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 调用initialValue获取初始化的值，此方法可以被子类重写, 如果不重写默认返回 null</span>    T value <span class="token operator">=</span> <span class="token function">initialValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    Thread t <span class="token operator">=</span> Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    ThreadLocalMap map <span class="token operator">=</span> <span class="token function">getMap</span><span class="token punctuation">(</span>t<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 判断 map 是否初始化过</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>map <span class="token operator">!=</span> null<span class="token punctuation">)</span>        <span class="token comment" spellcheck="true">// 存在则调用 map.set 设置此实体 entry，value 是默认的值</span>        map<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> value<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">else</span>        <span class="token comment" spellcheck="true">// 调用 createMap 进行 ThreadLocalMap 对象的初始化中</span>        <span class="token function">createMap</span><span class="token punctuation">(</span>t<span class="token punctuation">,</span> value<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 返回线程与当前 threadLocal 关联的局部变量</span>    <span class="token keyword">return</span> value<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p><code>remove()</code>：移除当前线程与当前 threadLocal 对象相关联的线程局部变量</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">remove</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 获取当前线程对象中维护的 ThreadLocalMap 对象</span>    ThreadLocalMap m <span class="token operator">=</span> <span class="token function">getMap</span><span class="token punctuation">(</span>Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>m <span class="token operator">!=</span> null<span class="token punctuation">)</span>        <span class="token comment" spellcheck="true">// map 存在则调用 map.remove，this时当前ThreadLocal，以this为key删除对应的实体</span>        m<span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li></ul><h3 id="LocalMap"><a href="#LocalMap" class="headerlink" title="LocalMap"></a>LocalMap</h3><h4 id="成员属性"><a href="#成员属性" class="headerlink" title="成员属性"></a>成员属性</h4><p>ThreadLocalMap 是 ThreadLocal 的内部类，没有实现 Map 接口，用独立的方式实现了 Map 的功能，其内部 Entry 也是独立实现</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// 初始化当前 map 内部散列表数组的初始长度 16</span><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> INITIAL_CAPACITY <span class="token operator">=</span> <span class="token number">16</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 存放数据的table，数组长度必须是2的整次幂。</span><span class="token keyword">private</span> Entry<span class="token punctuation">[</span><span class="token punctuation">]</span> table<span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 数组里面 entrys 的个数，可以用于判断 table 当前使用量是否超过阈值</span><span class="token keyword">private</span> <span class="token keyword">int</span> size <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 进行扩容的阈值，表使用量大于它的时候进行扩容。</span><span class="token keyword">private</span> <span class="token keyword">int</span> threshold<span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>存储结构 Entry：</p><ul><li>Entry 继承 WeakReference，key 是弱引用，目的是将 ThreadLocal 对象的生命周期和线程生命周期解绑</li><li>Entry 限制只能用 ThreadLocal 作为 key，key 为 null (entry.get() &#x3D;&#x3D; null) 意味着 key 不再被引用，entry 也可以从 table 中清除</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">Entry</span> <span class="token keyword">extends</span> <span class="token class-name">WeakReference</span><span class="token operator">&lt;</span>ThreadLocal<span class="token operator">&lt;</span><span class="token operator">?</span><span class="token operator">>></span> <span class="token punctuation">{</span>    Object value<span class="token punctuation">;</span>    <span class="token function">Entry</span><span class="token punctuation">(</span>ThreadLocal<span class="token operator">&lt;</span><span class="token operator">?</span><span class="token operator">></span> k<span class="token punctuation">,</span> Object v<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// this.referent = referent = key;</span>        <span class="token keyword">super</span><span class="token punctuation">(</span>k<span class="token punctuation">)</span><span class="token punctuation">;</span>        value <span class="token operator">=</span> v<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>构造方法：延迟初始化的，线程第一次存储 threadLocal - value 时才会创建 threadLocalMap 对象</p><pre class="line-numbers language-java"><code class="language-java"><span class="token function">ThreadLocalMap</span><span class="token punctuation">(</span>ThreadLocal<span class="token operator">&lt;</span><span class="token operator">?</span><span class="token operator">></span> firstKey<span class="token punctuation">,</span> Object firstValue<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 初始化table，创建一个长度为16的Entry数组</span>    table <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Entry</span><span class="token punctuation">[</span>INITIAL_CAPACITY<span class="token punctuation">]</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 【寻址算法】计算索引</span>    <span class="token keyword">int</span> i <span class="token operator">=</span> firstKey<span class="token punctuation">.</span>threadLocalHashCode <span class="token operator">&amp;</span> <span class="token punctuation">(</span>INITIAL_CAPACITY <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 创建 entry 对象，存放到指定位置的 slot 中</span>    table<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Entry</span><span class="token punctuation">(</span>firstKey<span class="token punctuation">,</span> firstValue<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 数据总量是 1</span>    size <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 将阈值设置为 （当前数组长度 * 2）/ 3。</span>    <span class="token function">setThreshold</span><span class="token punctuation">(</span>INITIAL_CAPACITY<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="成员方法-1"><a href="#成员方法-1" class="headerlink" title="成员方法"></a>成员方法</h4><ul><li><p><code>set()</code>：添加数据，ThreadLocalMap 使用<strong>线性探测法来解决哈希冲突</strong></p><ul><li><p>该方法会一直探测下一个地址，直到有空的地址后插入，若插入后 Map 数量超过阈值，数组会扩容为原来的 2 倍</p><p>假设当前 table 长度为16，计算出来 key 的 hash 值为 14，如果 table[14] 上已经有值，并且其 key 与当前 key 不一致，那么就发生了 hash 冲突，这个时候将 14 加 1 得到 15，取 table[15] 进行判断，如果还是冲突会回到 0，取 table[0]，以此类推，直到可以插入，可以把 Entry[]  table 看成一个<strong>环形数组</strong></p></li><li><p>线性探测法会出现<strong>堆积问题</strong>，可以采取平方探测法解决</p></li><li><p>在探测过程中 ThreadLocal 会复用 key 为 null 的脏 Entry 对象，并进行垃圾清理，防止出现内存泄漏</p></li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">set</span><span class="token punctuation">(</span>ThreadLocal<span class="token operator">&lt;</span><span class="token operator">?</span><span class="token operator">></span> key<span class="token punctuation">,</span> Object value<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 获取散列表</span>    ThreadLocal<span class="token punctuation">.</span>ThreadLocalMap<span class="token punctuation">.</span>Entry<span class="token punctuation">[</span><span class="token punctuation">]</span> tab <span class="token operator">=</span> table<span class="token punctuation">;</span>    <span class="token keyword">int</span> len <span class="token operator">=</span> tab<span class="token punctuation">.</span>length<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 哈希寻址</span>    <span class="token keyword">int</span> i <span class="token operator">=</span> key<span class="token punctuation">.</span>threadLocalHashCode <span class="token operator">&amp;</span> <span class="token punctuation">(</span>len<span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 使用线性探测法向后查找元素，碰到 entry 为空时停止探测</span>    <span class="token keyword">for</span> <span class="token punctuation">(</span>ThreadLocal<span class="token punctuation">.</span>ThreadLocalMap<span class="token punctuation">.</span>Entry e <span class="token operator">=</span> tab<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">;</span> e <span class="token operator">!=</span> null<span class="token punctuation">;</span> e <span class="token operator">=</span> tab<span class="token punctuation">[</span>i <span class="token operator">=</span> <span class="token function">nextIndex</span><span class="token punctuation">(</span>i<span class="token punctuation">,</span> len<span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 获取当前元素 key</span>        ThreadLocal<span class="token operator">&lt;</span><span class="token operator">?</span><span class="token operator">></span> k <span class="token operator">=</span> e<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// ThreadLocal 对应的 key 存在，【直接覆盖之前的值】</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>k <span class="token operator">==</span> key<span class="token punctuation">)</span> <span class="token punctuation">{</span>            e<span class="token punctuation">.</span>value <span class="token operator">=</span> value<span class="token punctuation">;</span>            <span class="token keyword">return</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 【这两个条件谁先成立不一定，所以 replaceStaleEntry 中还需要判断 k == key 的情况】</span>                <span class="token comment" spellcheck="true">// key 为 null，但是值不为 null，说明之前的 ThreadLocal 对象已经被回收了，当前是【过期数据】</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>k <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 【碰到一个过期的 slot，当前数据复用该槽位，替换过期数据】</span>            <span class="token comment" spellcheck="true">// 这个方法还进行了垃圾清理动作，防止内存泄漏</span>            <span class="token function">replaceStaleEntry</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> value<span class="token punctuation">,</span> i<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">return</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">// 逻辑到这说明碰到 slot == null 的位置，则在空元素的位置创建一个新的 Entry</span>    tab<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Entry</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> value<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 数量 + 1</span>    <span class="token keyword">int</span> sz <span class="token operator">=</span> <span class="token operator">++</span>size<span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 【做一次启发式清理】，如果没有清除任何 entry 并且【当前使用量达到了负载因子所定义，那么进行 rehash</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">cleanSomeSlots</span><span class="token punctuation">(</span>i<span class="token punctuation">,</span> sz<span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> sz <span class="token operator">>=</span> threshold<span class="token punctuation">)</span>        <span class="token comment" spellcheck="true">// 扩容</span>        <span class="token function">rehash</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// 获取【环形数组】的下一个索引</span><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">int</span> <span class="token function">nextIndex</span><span class="token punctuation">(</span><span class="token keyword">int</span> i<span class="token punctuation">,</span> <span class="token keyword">int</span> len<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 索引越界后从 0 开始继续获取</span>    <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>i <span class="token operator">+</span> <span class="token number">1</span> <span class="token operator">&lt;</span> len<span class="token punctuation">)</span> <span class="token operator">?</span> i <span class="token operator">+</span> <span class="token number">1</span> <span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// 在指定位置插入指定的数据</span><span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">replaceStaleEntry</span><span class="token punctuation">(</span>ThreadLocal<span class="token operator">&lt;</span><span class="token operator">?</span><span class="token operator">></span> key<span class="token punctuation">,</span> Object value<span class="token punctuation">,</span> <span class="token keyword">int</span> staleSlot<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 获取散列表</span>    Entry<span class="token punctuation">[</span><span class="token punctuation">]</span> tab <span class="token operator">=</span> table<span class="token punctuation">;</span>    <span class="token keyword">int</span> len <span class="token operator">=</span> tab<span class="token punctuation">.</span>length<span class="token punctuation">;</span>    Entry e<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 探测式清理的开始下标，默认从当前 staleSlot 开始</span>    <span class="token keyword">int</span> slotToExpunge <span class="token operator">=</span> staleSlot<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 以当前 staleSlot 开始【向前迭代查找】，找到索引靠前过期数据，找到以后替换 slotToExpunge 值</span>    <span class="token comment" spellcheck="true">// 【保证在一个区间段内，从最前面的过期数据开始清理】</span>    <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token function">prevIndex</span><span class="token punctuation">(</span>staleSlot<span class="token punctuation">,</span> len<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">(</span>e <span class="token operator">=</span> tab<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token operator">!=</span> null<span class="token punctuation">;</span> i <span class="token operator">=</span> <span class="token function">prevIndex</span><span class="token punctuation">(</span>i<span class="token punctuation">,</span> len<span class="token punctuation">)</span><span class="token punctuation">)</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>e<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">==</span> null<span class="token punctuation">)</span>            slotToExpunge <span class="token operator">=</span> i<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 以 staleSlot 【向后去查找】，直到碰到 null 为止，还是线性探测</span>    <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token function">nextIndex</span><span class="token punctuation">(</span>staleSlot<span class="token punctuation">,</span> len<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">(</span>e <span class="token operator">=</span> tab<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token operator">!=</span> null<span class="token punctuation">;</span> i <span class="token operator">=</span> <span class="token function">nextIndex</span><span class="token punctuation">(</span>i<span class="token punctuation">,</span> len<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 获取当前节点的 key</span>        ThreadLocal<span class="token operator">&lt;</span><span class="token operator">?</span><span class="token operator">></span> k <span class="token operator">=</span> e<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 条件成立说明是【替换逻辑】</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>k <span class="token operator">==</span> key<span class="token punctuation">)</span> <span class="token punctuation">{</span>            e<span class="token punctuation">.</span>value <span class="token operator">=</span> value<span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">// 因为本来要在 staleSlot 索引处插入该数据，现在找到了i索引处的key与数据一致</span>            <span class="token comment" spellcheck="true">// 但是 i 位置距离正确的位置更远，因为是向后查找，所以还是要在 staleSlot 位置插入当前 entry</span>            <span class="token comment" spellcheck="true">// 然后将 table[staleSlot] 这个过期数据放到当前循环到的 table[i] 这个位置，</span>            tab<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> tab<span class="token punctuation">[</span>staleSlot<span class="token punctuation">]</span><span class="token punctuation">;</span>            tab<span class="token punctuation">[</span>staleSlot<span class="token punctuation">]</span> <span class="token operator">=</span> e<span class="token punctuation">;</span>                        <span class="token comment" spellcheck="true">// 条件成立说明向前查找过期数据并未找到过期的 entry，但 staleSlot 位置已经不是过期数据了，i 位置才是</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span>slotToExpunge <span class="token operator">==</span> staleSlot<span class="token punctuation">)</span>                slotToExpunge <span class="token operator">=</span> i<span class="token punctuation">;</span>                        <span class="token comment" spellcheck="true">// 【清理过期数据，expungeStaleEntry 探测式清理，cleanSomeSlots 启发式清理】</span>            <span class="token function">cleanSomeSlots</span><span class="token punctuation">(</span><span class="token function">expungeStaleEntry</span><span class="token punctuation">(</span>slotToExpunge<span class="token punctuation">)</span><span class="token punctuation">,</span> len<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">return</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 条件成立说明当前遍历的 entry 是一个过期数据，并且该位置前面也没有过期数据</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>k <span class="token operator">==</span> null <span class="token operator">&amp;&amp;</span> slotToExpunge <span class="token operator">==</span> staleSlot<span class="token punctuation">)</span>            <span class="token comment" spellcheck="true">// 探测式清理过期数据的开始下标修改为当前循环的 index，因为 staleSlot 会放入要添加的数据</span>            slotToExpunge <span class="token operator">=</span> i<span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">// 向后查找过程中并未发现 k == key 的 entry，说明当前是一个【取代过期数据逻辑】</span>    <span class="token comment" spellcheck="true">// 删除原有的数据引用，防止内存泄露</span>    tab<span class="token punctuation">[</span>staleSlot<span class="token punctuation">]</span><span class="token punctuation">.</span>value <span class="token operator">=</span> null<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// staleSlot 位置添加数据，【上面的所有逻辑都不会更改 staleSlot 的值】</span>    tab<span class="token punctuation">[</span>staleSlot<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Entry</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> value<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 条件成立说明除了 staleSlot 以外，还发现其它的过期 slot，所以要【开启清理数据的逻辑】</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>slotToExpunge <span class="token operator">!=</span> staleSlot<span class="token punctuation">)</span>        <span class="token function">cleanSomeSlots</span><span class="token punctuation">(</span><span class="token function">expungeStaleEntry</span><span class="token punctuation">(</span>slotToExpunge<span class="token punctuation">)</span><span class="token punctuation">,</span> len<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><img src="https://img.jwt1399.top/img/202301032135926.png"></p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">int</span> <span class="token function">prevIndex</span><span class="token punctuation">(</span><span class="token keyword">int</span> i<span class="token punctuation">,</span> <span class="token keyword">int</span> len<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 形成一个环绕式的访问，头索引越界后置为尾索引</span>    <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>i <span class="token operator">-</span> <span class="token number">1</span> <span class="token operator">>=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token operator">?</span> i <span class="token operator">-</span> <span class="token number">1</span> <span class="token operator">:</span> len <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre></li><li><p><code>getEntry()</code>：ThreadLocal 的 get 方法以当前的 ThreadLocal 为 key，调用 getEntry 获取对应的存储实体 e</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> Entry <span class="token function">getEntry</span><span class="token punctuation">(</span>ThreadLocal<span class="token operator">&lt;</span><span class="token operator">?</span><span class="token operator">></span> key<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 哈希寻址</span>    <span class="token keyword">int</span> i <span class="token operator">=</span> key<span class="token punctuation">.</span>threadLocalHashCode <span class="token operator">&amp;</span> <span class="token punctuation">(</span>table<span class="token punctuation">.</span>length <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 访问散列表中指定指定位置的 slot </span>    Entry e <span class="token operator">=</span> table<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 条件成立，说明 slot 有值并且 key 就是要寻找的 key，直接返回</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>e <span class="token operator">!=</span> null <span class="token operator">&amp;&amp;</span> e<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">==</span> key<span class="token punctuation">)</span>        <span class="token keyword">return</span> e<span class="token punctuation">;</span>    <span class="token keyword">else</span>        <span class="token comment" spellcheck="true">// 进行线性探测</span>        <span class="token keyword">return</span> <span class="token function">getEntryAfterMiss</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> i<span class="token punctuation">,</span> e<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">// 线性探测寻址</span><span class="token keyword">private</span> Entry <span class="token function">getEntryAfterMiss</span><span class="token punctuation">(</span>ThreadLocal<span class="token operator">&lt;</span><span class="token operator">?</span><span class="token operator">></span> key<span class="token punctuation">,</span> <span class="token keyword">int</span> i<span class="token punctuation">,</span> Entry e<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 获取散列表</span>    Entry<span class="token punctuation">[</span><span class="token punctuation">]</span> tab <span class="token operator">=</span> table<span class="token punctuation">;</span>    <span class="token keyword">int</span> len <span class="token operator">=</span> tab<span class="token punctuation">.</span>length<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 开始遍历，碰到 slot == null 的情况，搜索结束</span>    <span class="token keyword">while</span> <span class="token punctuation">(</span>e <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 获取当前 slot 中 entry 对象的 key</span>        ThreadLocal<span class="token operator">&lt;</span><span class="token operator">?</span><span class="token operator">></span> k <span class="token operator">=</span> e<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 条件成立说明找到了，直接返回</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>k <span class="token operator">==</span> key<span class="token punctuation">)</span>            <span class="token keyword">return</span> e<span class="token punctuation">;</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>k <span class="token operator">==</span> null<span class="token punctuation">)</span>             <span class="token comment" spellcheck="true">// 过期数据，【探测式过期数据回收】</span>            <span class="token function">expungeStaleEntry</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">else</span>            <span class="token comment" spellcheck="true">// 更新 index 继续向后走</span>            i <span class="token operator">=</span> <span class="token function">nextIndex</span><span class="token punctuation">(</span>i<span class="token punctuation">,</span> len<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 获取下一个槽位中的 entry</span>        e <span class="token operator">=</span> tab<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">// 说明当前区段没有找到相应数据</span>    <span class="token comment" spellcheck="true">// 【因为存放数据是线性的向后寻找槽位，都是紧挨着的，不可能越过一个 空槽位 在后面放】，可以减少遍历的次数</span>    <span class="token keyword">return</span> null<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p><code>rehash()</code>：触发一次全量清理，如果数组长度大于等于长度的 <code>2/3 * 3/4 = 1/2</code>，则进行 resize</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">rehash</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 清楚当前散列表内的【所有】过期的数据</span>    <span class="token function">expungeStaleEntries</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// threshold = len * 2 / 3，就是 2/3 * (1 - 1/4)</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>size <span class="token operator">>=</span> threshold <span class="token operator">-</span> threshold <span class="token operator">/</span> <span class="token number">4</span><span class="token punctuation">)</span>        <span class="token function">resize</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">expungeStaleEntries</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    Entry<span class="token punctuation">[</span><span class="token punctuation">]</span> tab <span class="token operator">=</span> table<span class="token punctuation">;</span>    <span class="token keyword">int</span> len <span class="token operator">=</span> tab<span class="token punctuation">.</span>length<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 【遍历所有的槽位，清理过期数据】</span>    <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> j <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> j <span class="token operator">&lt;</span> len<span class="token punctuation">;</span> j<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        Entry e <span class="token operator">=</span> tab<span class="token punctuation">[</span>j<span class="token punctuation">]</span><span class="token punctuation">;</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>e <span class="token operator">!=</span> null <span class="token operator">&amp;&amp;</span> e<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">==</span> null<span class="token punctuation">)</span>            <span class="token function">expungeStaleEntry</span><span class="token punctuation">(</span>j<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>Entry <strong>数组扩容为原来的 2 倍</strong> ，重新计算 key 的散列值，如果遇到 key 为 null 的情况，会将其 value 也置为 null，帮助 GC</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">resize</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    Entry<span class="token punctuation">[</span><span class="token punctuation">]</span> oldTab <span class="token operator">=</span> table<span class="token punctuation">;</span>    <span class="token keyword">int</span> oldLen <span class="token operator">=</span> oldTab<span class="token punctuation">.</span>length<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 新数组的长度是老数组的二倍</span>    <span class="token keyword">int</span> newLen <span class="token operator">=</span> oldLen <span class="token operator">*</span> <span class="token number">2</span><span class="token punctuation">;</span>    Entry<span class="token punctuation">[</span><span class="token punctuation">]</span> newTab <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Entry</span><span class="token punctuation">[</span>newLen<span class="token punctuation">]</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 统计新table中的entry数量</span>    <span class="token keyword">int</span> count <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 遍历老表，进行【数据迁移】</span>    <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> j <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> j <span class="token operator">&lt;</span> oldLen<span class="token punctuation">;</span> <span class="token operator">++</span>j<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 访问老表的指定位置的 entry</span>        Entry e <span class="token operator">=</span> oldTab<span class="token punctuation">[</span>j<span class="token punctuation">]</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 条件成立说明老表中该位置有数据，可能是过期数据也可能不是</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>e <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>            ThreadLocal<span class="token operator">&lt;</span><span class="token operator">?</span><span class="token operator">></span> k <span class="token operator">=</span> e<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">// 过期数据</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span>k <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>                e<span class="token punctuation">.</span>value <span class="token operator">=</span> null<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// Help the GC</span>            <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>                <span class="token comment" spellcheck="true">// 非过期数据，在新表中进行哈希寻址</span>                <span class="token keyword">int</span> h <span class="token operator">=</span> k<span class="token punctuation">.</span>threadLocalHashCode <span class="token operator">&amp;</span> <span class="token punctuation">(</span>newLen <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token comment" spellcheck="true">// 【线程探测】</span>                <span class="token keyword">while</span> <span class="token punctuation">(</span>newTab<span class="token punctuation">[</span>h<span class="token punctuation">]</span> <span class="token operator">!=</span> null<span class="token punctuation">)</span>                    h <span class="token operator">=</span> <span class="token function">nextIndex</span><span class="token punctuation">(</span>h<span class="token punctuation">,</span> newLen<span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token comment" spellcheck="true">// 将数据存放到新表合适的 slot 中</span>                newTab<span class="token punctuation">[</span>h<span class="token punctuation">]</span> <span class="token operator">=</span> e<span class="token punctuation">;</span>                count<span class="token operator">++</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">// 设置下一次触发扩容的指标：threshold = len * 2 / 3;</span>    <span class="token function">setThreshold</span><span class="token punctuation">(</span>newLen<span class="token punctuation">)</span><span class="token punctuation">;</span>    size <span class="token operator">=</span> count<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 将扩容后的新表赋值给 threadLocalMap 内部散列表数组引用</span>    table <span class="token operator">=</span> newTab<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p><code>remove()</code>：删除 Entry</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">remove</span><span class="token punctuation">(</span>ThreadLocal<span class="token operator">&lt;</span><span class="token operator">?</span><span class="token operator">></span> key<span class="token punctuation">)</span> <span class="token punctuation">{</span>    Entry<span class="token punctuation">[</span><span class="token punctuation">]</span> tab <span class="token operator">=</span> table<span class="token punctuation">;</span>    <span class="token keyword">int</span> len <span class="token operator">=</span> tab<span class="token punctuation">.</span>length<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 哈希寻址</span>    <span class="token keyword">int</span> i <span class="token operator">=</span> key<span class="token punctuation">.</span>threadLocalHashCode <span class="token operator">&amp;</span> <span class="token punctuation">(</span>len<span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">for</span> <span class="token punctuation">(</span>Entry e <span class="token operator">=</span> tab<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">;</span> e <span class="token operator">!=</span> null<span class="token punctuation">;</span> e <span class="token operator">=</span> tab<span class="token punctuation">[</span>i <span class="token operator">=</span> <span class="token function">nextIndex</span><span class="token punctuation">(</span>i<span class="token punctuation">,</span> len<span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 找到了对应的 key</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>e<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">==</span> key<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 设置 key 为 null</span>            e<span class="token punctuation">.</span><span class="token function">clear</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">// 探测式清理</span>            <span class="token function">expungeStaleEntry</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">return</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li></ul><h4 id="清理方法"><a href="#清理方法" class="headerlink" title="清理方法"></a>清理方法</h4><ul><li><p>探测式清理：沿着开始位置向后探测清理过期数据，沿途中碰到未过期数据则将此数据 rehash 在 table 数组中的定位，重定位后的元素理论上更接近 <code>i = entry.key &amp; (table.length - 1)</code>，让<strong>数据的排列更紧凑</strong>，会优化整个散列表查询性能</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// table[staleSlot] 是一个过期数据，以这个位置开始继续向后查找过期数据</span><span class="token keyword">private</span> <span class="token keyword">int</span> <span class="token function">expungeStaleEntry</span><span class="token punctuation">(</span><span class="token keyword">int</span> staleSlot<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 获取散列表和数组长度</span>    Entry<span class="token punctuation">[</span><span class="token punctuation">]</span> tab <span class="token operator">=</span> table<span class="token punctuation">;</span>    <span class="token keyword">int</span> len <span class="token operator">=</span> tab<span class="token punctuation">.</span>length<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// help gc，先把当前过期的 entry 置空，在取消对 entry 的引用</span>    tab<span class="token punctuation">[</span>staleSlot<span class="token punctuation">]</span><span class="token punctuation">.</span>value <span class="token operator">=</span> null<span class="token punctuation">;</span>    tab<span class="token punctuation">[</span>staleSlot<span class="token punctuation">]</span> <span class="token operator">=</span> null<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 数量-1</span>    size<span class="token operator">--</span><span class="token punctuation">;</span>    Entry e<span class="token punctuation">;</span>    <span class="token keyword">int</span> i<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 从 staleSlot 开始向后遍历，直到碰到 slot == null 结束，【区间内清理过期数据】</span>    <span class="token keyword">for</span> <span class="token punctuation">(</span>i <span class="token operator">=</span> <span class="token function">nextIndex</span><span class="token punctuation">(</span>staleSlot<span class="token punctuation">,</span> len<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">(</span>e <span class="token operator">=</span> tab<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token operator">!=</span> null<span class="token punctuation">;</span> i <span class="token operator">=</span> <span class="token function">nextIndex</span><span class="token punctuation">(</span>i<span class="token punctuation">,</span> len<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        ThreadLocal<span class="token operator">&lt;</span><span class="token operator">?</span><span class="token operator">></span> k <span class="token operator">=</span> e<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 当前 entry 是过期数据</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>k <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// help gc</span>            e<span class="token punctuation">.</span>value <span class="token operator">=</span> null<span class="token punctuation">;</span>            tab<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> null<span class="token punctuation">;</span>            size<span class="token operator">--</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 当前 entry 不是过期数据的逻辑，【rehash】</span>            <span class="token comment" spellcheck="true">// 重新计算当前 entry 对应的 index</span>            <span class="token keyword">int</span> h <span class="token operator">=</span> k<span class="token punctuation">.</span>threadLocalHashCode <span class="token operator">&amp;</span> <span class="token punctuation">(</span>len <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">// 条件成立说明当前 entry 存储时发生过 hash 冲突，向后偏移过了</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span>h <span class="token operator">!=</span> i<span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token comment" spellcheck="true">// 当前位置置空</span>                tab<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> null<span class="token punctuation">;</span>                <span class="token comment" spellcheck="true">// 以正确位置 h 开始，向后查找第一个可以存放 entry 的位置</span>                <span class="token keyword">while</span> <span class="token punctuation">(</span>tab<span class="token punctuation">[</span>h<span class="token punctuation">]</span> <span class="token operator">!=</span> null<span class="token punctuation">)</span>                    h <span class="token operator">=</span> <span class="token function">nextIndex</span><span class="token punctuation">(</span>h<span class="token punctuation">,</span> len<span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token comment" spellcheck="true">// 将当前元素放入到【距离正确位置更近的位置，有可能就是正确位置】</span>                tab<span class="token punctuation">[</span>h<span class="token punctuation">]</span> <span class="token operator">=</span> e<span class="token punctuation">;</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">// 返回 slot = null 的槽位索引，图例是 7，这个索引代表【索引前面的区间已经清理完成垃圾了】</span>    <span class="token keyword">return</span> i<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><img src="https://img.jwt1399.top/img/202301040014250.png" style="zoom:67%;" /><img src="https://img.jwt1399.top/img/202301040014047.png" style="zoom:67%;" /></li><li><p>启发式清理：向后循环扫描过期数据，发现过期数据调用探测式清理方法，如果连续几次的循环都没有发现过期数据，就停止扫描</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">//  i 表示启发式清理工作开始位置，一般是空 slot，n 一般传递的是 table.length </span><span class="token keyword">private</span> <span class="token keyword">boolean</span> <span class="token function">cleanSomeSlots</span><span class="token punctuation">(</span><span class="token keyword">int</span> i<span class="token punctuation">,</span> <span class="token keyword">int</span> n<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 表示启发式清理工作是否清除了过期数据</span>    <span class="token keyword">boolean</span> removed <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 获取当前 map 的散列表引用</span>    Entry<span class="token punctuation">[</span><span class="token punctuation">]</span> tab <span class="token operator">=</span> table<span class="token punctuation">;</span>    <span class="token keyword">int</span> len <span class="token operator">=</span> tab<span class="token punctuation">.</span>length<span class="token punctuation">;</span>    <span class="token keyword">do</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 获取下一个索引，因为探测式返回的 slot 为 null</span>        i <span class="token operator">=</span> <span class="token function">nextIndex</span><span class="token punctuation">(</span>i<span class="token punctuation">,</span> len<span class="token punctuation">)</span><span class="token punctuation">;</span>        Entry e <span class="token operator">=</span> tab<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 条件成立说明是过期的数据，key 被 gc 了</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>e <span class="token operator">!=</span> null <span class="token operator">&amp;&amp;</span> e<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 【发现过期数据重置 n 为数组的长度】</span>            n <span class="token operator">=</span> len<span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">// 表示清理过过期数据</span>            removed <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">// 以当前过期的 slot 为开始节点 做一次探测式清理工作</span>            i <span class="token operator">=</span> <span class="token function">expungeStaleEntry</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 假设 table 长度为 16</span>        <span class="token comment" spellcheck="true">// 16 >>> 1 ==> 8，8 >>> 1 ==> 4，4 >>> 1 ==> 2，2 >>> 1 ==> 1，1 >>> 1 ==> 0</span>        <span class="token comment" spellcheck="true">// 连续经过这么多次循环【没有扫描到过期数据】，就停止循环，扫描到空 slot 不算，因为不是过期数据</span>    <span class="token punctuation">}</span> <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>n <span class="token operator">>>>=</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">!=</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 返回清除标记</span>    <span class="token keyword">return</span> removed<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li></ul><h1 id="⑤线程池"><a href="#⑤线程池" class="headerlink" title="⑤线程池"></a>⑤线程池</h1><h2 id="❶基本概述"><a href="#❶基本概述" class="headerlink" title="❶基本概述"></a>❶基本概述</h2><p>线程池：一个容纳多个线程的容器，容器中的线程可以重复使用，省去了频繁创建和销毁线程对象的操作</p><p>线程池作用：</p><ol><li>降低资源消耗，减少了创建和销毁线程的次数，每个工作线程都可以被重复利用，可执行多个任务</li><li>提高响应速度，当任务到达时，如果有线程可以直接用，不会出现系统僵死</li><li>提高线程的可管理性，如果无限制的创建线程，不仅会消耗系统资源，还会降低系统的稳定性，使用线程池可以进行统一的分配，调优和监控</li></ol><p>线程池的核心思想：<strong>线程复用</strong>，同一个线程可以被重复使用，来处理多个任务</p><p>池化技术 (Pool) ：一种编程技巧，核心思想是资源复用，在请求量大时能优化应用性能，降低系统频繁建连的资源开销</p><h2 id="❷阻塞队列-BlockingQueue"><a href="#❷阻塞队列-BlockingQueue" class="headerlink" title="❷阻塞队列-BlockingQueue"></a>❷阻塞队列-BlockingQueue</h2><h3 id="基本介绍-1"><a href="#基本介绍-1" class="headerlink" title="基本介绍"></a>基本介绍</h3><p>有界队列和无界队列：</p><ul><li><p>有界队列：有固定大小的队列，比如设定了固定大小的 LinkedBlockingQueue，又或者大小为 0</p></li><li><p>无界队列：没有设置固定大小的队列，这些队列可以直接入队，直到溢出（超过 Integer.MAX_VALUE），所以相当于无界</p></li></ul><p><code>java.util.concurrent.BlockingQueue</code> 接口有以下阻塞队列的实现：<strong>FIFO 队列</strong> </p><ul><li>ArrayBlockQueue：由数组结构组成的有界阻塞队列</li><li>LinkedBlockingQueue：由链表结构组成的无界（默认大小 Integer.MAX_VALUE）的阻塞队列</li><li>PriorityBlockQueue：支持优先级排序的无界阻塞队列</li><li>DelayedWorkQueue：使用优先级队列实现的延迟无界阻塞队列</li><li>SynchronousQueue：不存储元素的阻塞队列，每一个生产线程会阻塞到有一个 put 的线程放入元素为止</li><li>LinkedTransferQueue：由链表结构组成的无界阻塞队列</li><li>LinkedBlockingDeque：由链表结构组成的<strong>双向</strong>阻塞队列</li></ul><p>与普通队列（LinkedList、ArrayList等）的不同点在于阻塞队列中阻塞添加和阻塞删除方法，以及线程安全：</p><ul><li>阻塞添加 put()：当阻塞队列元素已满时，添加队列元素的线程会被阻塞，直到队列元素不满时才重新唤醒线程执行</li><li>阻塞删除 take()：在队列元素为空时，删除队列元素的线程将被阻塞，直到队列不为空再执行删除操作（一般会返回被删除的元素)</li></ul><h3 id="核心方法"><a href="#核心方法" class="headerlink" title="核心方法"></a>核心方法</h3><p>BlockingQueue 具有 4 组不同的方法用于插入、移除以及对队列中的元素进行检查。如果请求的操作不能得到立即执行的话，每个方法的表现也不同。这些方法如下:</p><table><thead><tr><th>方法类型</th><th>抛出异常</th><th>特殊值</th><th>阻塞</th><th>超时</th></tr></thead><tbody><tr><td>插入（尾）</td><td>add(e)</td><td>offer(e)</td><td>put(e)</td><td>offer(e,time,unit)</td></tr><tr><td>移除（头）</td><td>remove()</td><td>poll()</td><td>take()</td><td>poll(time,unit)</td></tr><tr><td>检查（队首元素）</td><td>element()</td><td>peek()</td><td>不可用</td><td>不可用</td></tr></tbody></table><ul><li>抛出异常组：<ul><li>当阻塞队列满时：在往队列中 add 插入元素会抛出 IllegalStateException: Queue full</li><li>当阻塞队列空时：再往队列中 remove 移除元素，会抛出 NoSuchException</li></ul></li><li>特殊值组：<ul><li>插入方法：成功 true，失败 false</li><li>移除方法：成功返回出队列元素，队列没有就返回 null</li></ul></li><li>阻塞组：<ul><li>当阻塞队列满时，生产者继续往队列里 put 元素，队列会一直阻塞生产线程直到队列有空间 put 数据或响应中断退出</li><li>当阻塞队列空时，消费者线程试图从队列里 take 元素，队列会一直阻塞消费者线程直到队列中有可用元素</li></ul></li><li>超时退出：当阻塞队列满时，队里会阻塞生产者线程一定时间，超过限时后生产者线程会退出</li></ul><h2 id="❸操作Pool"><a href="#❸操作Pool" class="headerlink" title="❸操作Pool"></a>❸操作Pool</h2><h3 id="创建方式"><a href="#创建方式" class="headerlink" title="创建方式"></a>创建方式</h3><h4 id="ThreadPoolExecutor"><a href="#ThreadPoolExecutor" class="headerlink" title="ThreadPoolExecutor"></a>ThreadPoolExecutor</h4><p>存放线程的容器：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">final</span> HashSet<span class="token operator">&lt;</span>Worker<span class="token operator">></span> workers <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">HashSet</span><span class="token operator">&lt;</span>Worker<span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>构造方法：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token function">ThreadPoolExecutor</span><span class="token punctuation">(</span><span class="token keyword">int</span> corePoolSize<span class="token punctuation">,</span>                          <span class="token keyword">int</span> maximumPoolSize<span class="token punctuation">,</span>                          <span class="token keyword">long</span> keepAliveTime<span class="token punctuation">,</span>                          TimeUnit unit<span class="token punctuation">,</span>                          BlockingQueue<span class="token operator">&lt;</span>Runnable<span class="token operator">></span> workQueue<span class="token punctuation">,</span>                          ThreadFactory threadFactory<span class="token punctuation">,</span>                          RejectedExecutionHandler handler<span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>参数介绍：</p><ul><li><p>corePoolSize：核心线程数，定义了最小可以同时运行的线程数量</p></li><li><p>maximumPoolSize：最大线程数，当队列中存放的任务达到队列容量时，当前可以同时运行的数量变为最大线程数，创建线程并立即执行最新的任务，与核心线程数之间的差值又叫救急线程数</p></li><li><p>keepAliveTime：救急线程最大存活时间，当线程池中的线程数量大于 <code>corePoolSize</code> 的时候，如果这时没有新的任务提交，核心线程外的线程不会立即销毁，而是会等到 <code>keepAliveTime</code> 时间超过销毁</p></li><li><p>unit：<code>keepAliveTime</code> 参数的时间单位</p></li><li><p>workQueue：阻塞队列，存放被提交但尚未被执行的任务</p></li><li><p>threadFactory：线程工厂，创建新线程时用到，可以为线程创建时起名字</p></li><li><p>handler：拒绝策略，线程到达最大线程数仍有新任务时会执行拒绝策略</p><p>RejectedExecutionHandler 下有 4 个实现类：</p><ul><li>AbortPolicy：让调用者抛出 RejectedExecutionException 异常，<strong>默认策略</strong></li><li>CallerRunsPolicy：让调用者运行的调节机制，将某些任务回退到调用者，从而降低新任务的流量</li><li>DiscardPolicy：直接丢弃任务，不予任何处理也不抛出异常</li><li>DiscardOldestPolicy：放弃队列中最早的任务，把当前任务加入队列中尝试再次提交当前任务</li></ul><p>补充：其他框架拒绝策略</p><ul><li>Dubbo：在抛出 RejectedExecutionException 异常前记录日志，并 dump 线程栈信息，方便定位问题</li><li>Netty：创建一个新线程来执行任务</li><li>ActiveMQ：带超时等待（60s）尝试放入队列</li><li>PinPoint：它使用了一个拒绝策略链，会逐一尝试策略链中每种拒绝策略</li></ul></li></ul><h4 id="Executors"><a href="#Executors" class="headerlink" title="Executors"></a>Executors</h4><p>Executors 提供了四种线程池的创建：newCachedThreadPool、newFixedThreadPool、newSingleThreadExecutor、newScheduledThreadPool</p><ul><li><p>newFixedThreadPool：创建一个拥有 n 个线程的线程池</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">static</span> ExecutorService <span class="token function">newFixedThreadPool</span><span class="token punctuation">(</span><span class="token keyword">int</span> nThreads<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">ThreadPoolExecutor</span><span class="token punctuation">(</span>nThreads<span class="token punctuation">,</span> nThreads<span class="token punctuation">,</span> 0L<span class="token punctuation">,</span> TimeUnit<span class="token punctuation">.</span>MILLISECONDS<span class="token punctuation">,</span>                                  <span class="token keyword">new</span> <span class="token class-name">LinkedBlockingQueue</span><span class="token operator">&lt;</span>Runnable<span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><ul><li>核心线程数 &#x3D;&#x3D; 最大线程数（没有救急线程被创建），因此也无需超时时间</li><li>LinkedBlockingQueue 是一个单向链表实现的阻塞队列，默认大小为 <code>Integer.MAX_VALUE</code>，也就是无界队列，可以放任意数量的任务，在任务比较多的时候会导致 OOM（内存溢出）</li><li>适用于任务量已知，相对耗时的长期任务</li></ul></li><li><p>newCachedThreadPool：创建一个可扩容的线程池</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">static</span> ExecutorService <span class="token function">newCachedThreadPool</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">ThreadPoolExecutor</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> Integer<span class="token punctuation">.</span>MAX_VALUE<span class="token punctuation">,</span> 60L<span class="token punctuation">,</span> TimeUnit<span class="token punctuation">.</span>SECONDS<span class="token punctuation">,</span>                                  <span class="token keyword">new</span> <span class="token class-name">SynchronousQueue</span><span class="token operator">&lt;</span>Runnable<span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><ul><li><p>核心线程数是 0， 最大线程数是 29 个 1，全部都是救急线程（60s 后可以回收），可能会创建大量线程，从而导致 <strong>OOM</strong></p></li><li><p>SynchronousQueue 作为阻塞队列，没有容量，对于每一个 take 的线程会阻塞直到有一个 put 的线程放入元素为止（类似一手交钱、一手交货）</p></li><li><p>适合任务数比较密集，但每个任务执行时间较短的情况</p></li></ul></li><li><p>newSingleThreadExecutor：创建一个只有 1 个线程的单线程池</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">static</span> ExecutorService <span class="token function">newSingleThreadExecutor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">FinalizableDelegatedExecutorService</span>        <span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">ThreadPoolExecutor</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">,</span>0L<span class="token punctuation">,</span> TimeUnit<span class="token punctuation">.</span>MILLISECONDS<span class="token punctuation">,</span>                                <span class="token keyword">new</span> <span class="token class-name">LinkedBlockingQueue</span><span class="token operator">&lt;</span>Runnable<span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>保证所有任务按照<strong>指定顺序执行</strong>，线程数固定为 1，任务数多于 1 时会放入无界队列排队，任务执行完毕，这唯一的线程也不会被释放</li></ul></li></ul><p>对比：</p><ul><li><p>创建一个单线程串行执行任务，如果任务执行失败而终止那么没有任何补救措施，线程池会新建一个线程，保证池的正常工作</p></li><li><p>Executors.newSingleThreadExecutor() 线程个数始终为 1，不能修改。FinalizableDelegatedExecutorService 应用的是装饰器模式，只对外暴露了 ExecutorService 接口，因此不能调用 ThreadPoolExecutor 中特有的方法</p><p>原因：父类不能直接调用子类中的方法，需要反射或者创建对象的方式，可以调用子类静态方法</p></li><li><p>Executors.newFixedThreadPool(1) 初始时为 1，可以修改。对外暴露的是 ThreadPoolExecutor 对象，可以强转后调用 setCorePoolSize 等方法进行修改</p></li></ul><p><img src="https://img.jwt1399.top/img/202301031741667.png"></p><h4 id="开发要求"><a href="#开发要求" class="headerlink" title="开发要求"></a>开发要求</h4><p>阿里巴巴 Java 开发手册要求：</p><ul><li><p><strong>线程资源必须通过线程池提供，不允许在应用中自行显式创建线程</strong></p><ul><li>使用线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销，解决资源不足的问题</li><li>如果不使用线程池，有可能造成系统创建大量同类线程而导致消耗完内存或者过度切换的问题</li></ul></li><li><p>线程池不允许使用 Executors 去创建，而是通过 ThreadPoolExecutor 的方式，这样的处理方式更加明确线程池的运行规则，规避资源耗尽的风险</p><p>Executors 返回的线程池对象弊端如下：</p><ul><li>FixedThreadPool 和 SingleThreadPool：请求队列长度为 Integer.MAX_VALUE，可能会堆积大量的请求，从而导致 OOM</li><li>CacheThreadPool 和 ScheduledThreadPool：允许创建线程数量为 Integer.MAX_VALUE，可能会创建大量的线程，导致 OOM</li></ul></li></ul><p>创建多大容量的线程池合适？</p><ul><li><p>一般来说池中<strong>总线程数是核心池线程数量两倍</strong>，确保当核心池有线程停止时，核心池外有线程进入核心池</p></li><li><p>过小会导致程序不能充分地利用系统资源、容易导致饥饿</p></li><li><p>过大会导致更多的线程上下文切换，占用更多内存</p><p>上下文切换：当前任务在执行完 CPU 时间片切换到另一个任务之前会先保存自己的状态，以便下次再切换回这个任务时，可以再加载这个任务的状态，任务从保存到再加载的过程就是一次上下文切换</p></li></ul><p>核心线程数常用公式：</p><ul><li><p><strong>CPU 密集型任务 (N+1)：</strong> 这种任务消耗的是 CPU 资源，可以将核心线程数设置为 N (CPU 核心数) + 1，比 CPU 核心数多出来的一个线程是为了防止线程发生缺页中断，或者其它原因导致的任务暂停而带来的影响。一旦任务暂停，CPU 某个核心就会处于空闲状态，而在这种情况下多出来的一个线程就可以充分利用 CPU 的空闲时间</p><p>CPU 密集型简单理解就是利用 CPU 计算能力的任务比如在内存中对大量数据进行分析</p></li><li><p><strong>I&#x2F;O 密集型任务：</strong> 这种系统 CPU 处于阻塞状态，用大部分的时间来处理 I&#x2F;O 交互，而线程在处理 I&#x2F;O 的时间段内不会占用 CPU 来处理，这时就可以将 CPU 交出给其它线程使用，因此在 I&#x2F;O 密集型任务的应用中，我们可以多配置一些线程，具体的计算方法是 2N 或 CPU 核数&#x2F; (1-阻塞系数)，阻塞系数在 0.8~0.9 之间</p><p>IO 密集型就是涉及到网络读取，文件读取此类任务 ，特点是 CPU 计算耗费时间相比于等待 IO 操作完成的时间来说很少，大部分时间都花在了等待 IO 操作完成上</p></li></ul><h3 id="提交方法"><a href="#提交方法" class="headerlink" title="提交方法"></a>提交方法</h3><p>ExecutorService 类 API：</p><table><thead><tr><th>方法</th><th>说明</th></tr></thead><tbody><tr><td>void execute(Runnable command)</td><td>执行任务（Executor 类 API）</td></tr><tr><td>Future&lt;?&gt; submit(Runnable task)</td><td>提交任务 task()</td></tr><tr><td>Future submit(Callable&lt;T&gt; task)</td><td>提交任务 task，用返回值 Future 获得任务执行结果</td></tr><tr><td>List&lt;Future&lt;T&gt;&gt; invokeAll(Collection&lt;? extends Callable&lt;T&gt;&gt; tasks)</td><td>提交 tasks 中所有任务</td></tr><tr><td>List&lt;Future&lt;T&gt;&gt; invokeAll(Collection&lt;? extends Callable&lt;T&gt;&gt; tasks, long timeout, TimeUnit unit)</td><td>提交 tasks 中所有任务，超时时间针对所有task，超时会取消没有执行完的任务，并抛出超时异常</td></tr><tr><td>T invokeAny(Collection&lt;? extends Callable&lt;T&gt;&gt; tasks)</td><td>提交 tasks 中所有任务，哪个任务先成功执行完毕，返回此任务执行结果，其它任务取消</td></tr></tbody></table><p>execute 和 submit 都属于线程池的方法，对比：</p><ul><li><p>execute 只能执行 Runnable 类型的任务，没有返回值； submit 既能提交 Runnable 类型任务也能提交 Callable 类型任务，会有一个类型为Future的返回值，但当任务类型为Runnable时，返回值为null。</p><ul><li>底层是<strong>封装成 FutureTask，然后调用 execute 执行</strong></li></ul></li><li><p>execute 会直接抛出任务执行时的异常，submit 会吞掉异常，可通过 Future 的 get 方法将任务执行时的异常重新抛出</p></li></ul><p><img src="https://img.jwt1399.top/img/202301031757459.png"></p><h3 id="关闭方法"><a href="#关闭方法" class="headerlink" title="关闭方法"></a>关闭方法</h3><p>ExecutorService 类 API：</p><table><thead><tr><th>方法</th><th>说明</th></tr></thead><tbody><tr><td>void shutdown()</td><td>线程池状态变为 SHUTDOWN，等待任务执行完后关闭线程池，不会接收新任务，但已提交任务会执行完，而且也可以添加线程（不绑定任务）</td></tr><tr><td>List&lt;Runnable&gt; shutdownNow()</td><td>线程池状态变为 STOP，用 interrupt 中断正在执行的任务，直接关闭线程池，不会接收新任务，会将队列中的任务返回</td></tr><tr><td>boolean isShutdown()</td><td>不在 RUNNING 状态的线程池，此执行者已被关闭，方法返回 true</td></tr><tr><td>boolean isTerminated()</td><td>线程池状态是否是 TERMINATED，如果所有任务在关闭后完成，返回 true</td></tr><tr><td>boolean awaitTermination(long timeout, TimeUnit unit)</td><td>调用 shutdown 后，由于调用线程不会等待所有任务运行结束，如果它想在线程池 TERMINATED 后做些事情，可以利用此方法等待</td></tr></tbody></table><h3 id="处理异常"><a href="#处理异常" class="headerlink" title="处理异常"></a>处理异常</h3><p>execute 会直接抛出任务执行时的异常，submit 会吞掉异常，有两种处理方法</p><p>方法 1：主动捉异常</p><pre class="line-numbers language-java"><code class="language-java">ExecutorService executorService <span class="token operator">=</span> Executors<span class="token punctuation">.</span><span class="token function">newFixedThreadPool</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>pool<span class="token punctuation">.</span><span class="token function">submit</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span>    <span class="token keyword">try</span> <span class="token punctuation">{</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"task1"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">1</span> <span class="token operator">/</span> <span class="token number">0</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Exception</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span>        e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>方法 2：使用 Future 对象</p><pre class="line-numbers language-java"><code class="language-java">ExecutorService executorService <span class="token operator">=</span> Executors<span class="token punctuation">.</span><span class="token function">newFixedThreadPool</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>Future<span class="token operator">&lt;</span><span class="token operator">?</span><span class="token operator">></span> future <span class="token operator">=</span> pool<span class="token punctuation">.</span><span class="token function">submit</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span>    System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"task1"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">1</span> <span class="token operator">/</span> <span class="token number">0</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>future<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="❹工作原理"><a href="#❹工作原理" class="headerlink" title="❹工作原理"></a>❹工作原理</h2><h3 id="运行流程"><a href="#运行流程" class="headerlink" title="运行流程"></a>运行流程</h3><p><img src="https://img.jwt1399.top/img/202301031724574.png"></p><ol><li><p>创建线程池，等待提交过来的任务请求（<strong>懒惰</strong>），调用 execute 方法才会创建线程【①】</p></li><li><p>当调用 execute() 方法添加一个请求任务时，线程池会做如下判断：</p><ul><li>如果正在运行的线程数量小于 corePoolSize，那么马上创建线程运行这个任务【②】</li><li>如果正在运行的线程数量大于或等于 corePoolSize，那么将这个任务放入队列【③】</li><li>如果这时队列满了且正在运行的线程数量还小于 maximumPoolSize，那么会创建非核心线程<strong>立刻运行这个任务</strong>，对于阻塞队列中的任务不公平。这是因为创建每个 Worker（线程）对象会绑定一个初始任务，启动 Worker 时会优先执行【④】</li><li>如果队列满了且正在运行的线程数量大于或等于 maximumPoolSize，那么线程池会启动饱和<strong>拒绝策略</strong>来执行【⑤】</li></ul></li><li><p>当一个线程完成任务时，会从队列中取下一个任务来执行【take&#x2F;poll】</p></li><li><p>当一个线程空闲超过一定的时间（keepAliveTime）时，线程池会判断：如果当前运行的线程数大于 corePoolSize，那么这个线程就被停掉，所以线程池的所有任务完成后最终会收缩到 corePoolSize 大小</p></li></ol><h3 id="状态信息"><a href="#状态信息" class="headerlink" title="状态信息"></a>状态信息</h3><p>ThreadPoolExecutor 使用 int 的<strong>高 3 位来表示线程池状态，低 29 位表示线程数量</strong>。这些信息存储在一个<code>原子变量 ctl</code> 中，目的是将线程池状态与线程个数合二为一，这样就可以用一次 CAS 原子操作进行赋值</p><ul><li><p>状态表示：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// 高3位：表示当前线程池运行状态，除去高3位之后的低位：表示当前线程池中所拥有的线程数量</span><span class="token keyword">private</span> <span class="token keyword">final</span> AtomicInteger ctl <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">AtomicInteger</span><span class="token punctuation">(</span><span class="token function">ctlOf</span><span class="token punctuation">(</span>RUNNING<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 表示在 ctl 中，低 COUNT_BITS 位，是用于存放当前线程数量的位</span><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> COUNT_BITS <span class="token operator">=</span> Integer<span class="token punctuation">.</span>SIZE <span class="token operator">-</span> <span class="token number">3</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 低 COUNT_BITS 位所能表达的最大数值，000 11111111111111111111 => 5亿多</span><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> CAPACITY   <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token number">1</span> <span class="token operator">&lt;&lt;</span> COUNT_BITS<span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><img src="https://img.jwt1399.top/img/202301031912289.png"></p></li><li><p>四种状态：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// 111 000000000000000000，转换成整数后其实就是一个【负数】</span><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> RUNNING    <span class="token operator">=</span> <span class="token operator">-</span><span class="token number">1</span> <span class="token operator">&lt;&lt;</span> COUNT_BITS<span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 000 000000000000000000</span><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> SHUTDOWN   <span class="token operator">=</span>  <span class="token number">0</span> <span class="token operator">&lt;&lt;</span> COUNT_BITS<span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 001 000000000000000000</span><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> STOP       <span class="token operator">=</span>  <span class="token number">1</span> <span class="token operator">&lt;&lt;</span> COUNT_BITS<span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 010 000000000000000000</span><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> TIDYING    <span class="token operator">=</span>  <span class="token number">2</span> <span class="token operator">&lt;&lt;</span> COUNT_BITS<span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 011 000000000000000000</span><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> TERMINATED <span class="token operator">=</span>  <span class="token number">3</span> <span class="token operator">&lt;&lt;</span> COUNT_BITS<span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><table><thead><tr><th>状态</th><th>高3位</th><th>接收新任务</th><th>处理阻塞任务队列</th><th>说明</th></tr></thead><tbody><tr><td>RUNNING</td><td>111</td><td>Y</td><td>Y</td><td></td></tr><tr><td>SHUTDOWN</td><td>000</td><td>N</td><td>Y</td><td>不接收新任务，但处理阻塞队列剩余任务</td></tr><tr><td>STOP</td><td>001</td><td>N</td><td>N</td><td>中断正在执行的任务，并抛弃阻塞队列任务</td></tr><tr><td>TIDYING</td><td>010</td><td>-</td><td>-</td><td>任务全执行完毕，活动线程为 0 即将进入终结</td></tr><tr><td>TERMINATED</td><td>011</td><td>-</td><td>-</td><td>终止状态</td></tr></tbody></table></li><li><p>获取当前线程池运行状态：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">int</span> <span class="token function">runStateOf</span><span class="token punctuation">(</span><span class="token keyword">int</span> c<span class="token punctuation">)</span>     <span class="token punctuation">{</span> <span class="token keyword">return</span> c <span class="token operator">&amp;</span> <span class="token operator">~</span>CAPACITY<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token comment" spellcheck="true">// ~CAPACITY = ~000 11111111111111111111 = 111 000000000000000000000（取反）</span><span class="token comment" spellcheck="true">// c == ctl = 111 000000000000000000111</span><span class="token comment" spellcheck="true">// 111 000000000000000000111</span><span class="token comment" spellcheck="true">// 111 000000000000000000000</span><span class="token comment" spellcheck="true">// 111 000000000000000000000获取到了运行状态</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>获取当前线程池线程数量：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">int</span> <span class="token function">workerCountOf</span><span class="token punctuation">(</span><span class="token keyword">int</span> c<span class="token punctuation">)</span>  <span class="token punctuation">{</span> <span class="token keyword">return</span> c <span class="token operator">&amp;</span> CAPACITY<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token comment" spellcheck="true">//        c = 111 000000000000000000111</span><span class="token comment" spellcheck="true">// CAPACITY = 000 111111111111111111111</span><span class="token comment" spellcheck="true">//            000 000000000000000000111 => 7</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>重置当前线程池状态 ctl：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// rs 表示线程池状态，wc 表示当前线程池中 worker（线程）数量，相与以后就是合并后的状态</span><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">int</span> <span class="token function">ctlOf</span><span class="token punctuation">(</span><span class="token keyword">int</span> rs<span class="token punctuation">,</span> <span class="token keyword">int</span> wc<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> rs <span class="token operator">|</span> wc<span class="token punctuation">;</span> <span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre></li><li><p>比较当前线程池 ctl 所表示的状态：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// 比较当前线程池 ctl 所表示的状态，是否小于某个状态 s</span><span class="token comment" spellcheck="true">// 状态对比：RUNNING &lt; SHUTDOWN &lt; STOP &lt; TIDYING &lt; TERMINATED</span><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">boolean</span> <span class="token function">runStateLessThan</span><span class="token punctuation">(</span><span class="token keyword">int</span> c<span class="token punctuation">,</span> <span class="token keyword">int</span> s<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> c <span class="token operator">&lt;</span> s<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token comment" spellcheck="true">// 比较当前线程池 ctl 所表示的状态，是否大于等于某个状态s</span><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">boolean</span> <span class="token function">runStateAtLeast</span><span class="token punctuation">(</span><span class="token keyword">int</span> c<span class="token punctuation">,</span> <span class="token keyword">int</span> s<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> c <span class="token operator">>=</span> s<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token comment" spellcheck="true">// 小于 SHUTDOWN 的一定是 RUNNING，SHUTDOWN == 0</span><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">boolean</span> <span class="token function">isRunning</span><span class="token punctuation">(</span><span class="token keyword">int</span> c<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> c <span class="token operator">&lt;</span> SHUTDOWN<span class="token punctuation">;</span> <span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>设置线程池 ctl：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// 使用 CAS 方式 让 ctl 值 +1 ，成功返回 true, 失败返回 false</span><span class="token keyword">private</span> <span class="token keyword">boolean</span> <span class="token function">compareAndIncrementWorkerCount</span><span class="token punctuation">(</span><span class="token keyword">int</span> expect<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">return</span> ctl<span class="token punctuation">.</span><span class="token function">compareAndSet</span><span class="token punctuation">(</span>expect<span class="token punctuation">,</span> expect <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">// 使用 CAS 方式 让 ctl 值 -1 ，成功返回 true, 失败返回 false</span><span class="token keyword">private</span> <span class="token keyword">boolean</span> <span class="token function">compareAndDecrementWorkerCount</span><span class="token punctuation">(</span><span class="token keyword">int</span> expect<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">return</span> ctl<span class="token punctuation">.</span><span class="token function">compareAndSet</span><span class="token punctuation">(</span>expect<span class="token punctuation">,</span> expect <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">// 将 ctl 值减一，do while 循环会一直重试，直到成功为止</span><span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">decrementWorkerCount</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">do</span> <span class="token punctuation">{</span><span class="token punctuation">}</span> <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">compareAndDecrementWorkerCount</span><span class="token punctuation">(</span>ctl<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li></ul><h3 id="成员属性-1"><a href="#成员属性-1" class="headerlink" title="成员属性"></a>成员属性</h3><p>成员变量</p><ul><li><p><strong>线程池中存放 Worker 的容器</strong>：线程池没有初始化，直接往池中加线程即可</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">final</span> HashSet<span class="token operator">&lt;</span>Worker<span class="token operator">></span> workers <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">HashSet</span><span class="token operator">&lt;</span>Worker<span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre></li><li><p>线程全局锁：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// 增加减少 worker 或者时修改线程池运行状态需要持有 mainLock</span><span class="token keyword">private</span> <span class="token keyword">final</span> ReentrantLock mainLock <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ReentrantLock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre></li><li><p>可重入锁的条件变量：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// 当外部线程调用 awaitTermination() 方法时，会等待当前线程池状态为 Termination 为止</span><span class="token keyword">private</span> <span class="token keyword">final</span> Condition termination <span class="token operator">=</span> mainLock<span class="token punctuation">.</span><span class="token function">newCondition</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre></li><li><p>线程池相关参数：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">volatile</span> <span class="token keyword">int</span> corePoolSize<span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 核心线程数量</span><span class="token keyword">private</span> <span class="token keyword">volatile</span> <span class="token keyword">int</span> maximumPoolSize<span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 线程池最大线程数量</span><span class="token keyword">private</span> <span class="token keyword">volatile</span> <span class="token keyword">long</span> keepAliveTime<span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 空闲线程存活时间</span><span class="token keyword">private</span> <span class="token keyword">volatile</span> ThreadFactory threadFactory<span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 创建线程时使用的线程工厂，默认是 DefaultThreadFactory</span><span class="token keyword">private</span> <span class="token keyword">final</span> BlockingQueue<span class="token operator">&lt;</span>Runnable<span class="token operator">></span> workQueue<span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 【超过核心线程提交任务就放入 阻塞队列】</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">volatile</span> RejectedExecutionHandler handler<span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 拒绝策略，juc包提供了4中方式</span><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> RejectedExecutionHandler defaultHandler <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">AbortPolicy</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 默认策略</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre></li><li><p>记录线程池相关属性的数值：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">int</span> largestPoolSize<span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 记录线程池生命周期内线程数最大值</span><span class="token keyword">private</span> <span class="token keyword">long</span> completedTaskCount<span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 记录线程池所完成任务总数，当某个 worker 退出时将完成的任务累加到该属性</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre></li><li><p>控制<strong>核心线程数量内的线程是否可以被回收</strong>：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// false（默认）代表不可以，为 true 时核心线程空闲超过 keepAliveTime 也会被回收</span><span class="token comment" spellcheck="true">// allowCoreThreadTimeOut(boolean value) 方法可以设置该值</span><span class="token keyword">private</span> <span class="token keyword">volatile</span> <span class="token keyword">boolean</span> allowCoreThreadTimeOut<span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre></li></ul><p>内部类：</p><ul><li><p>Worker 类：<strong>每个 Worker 对象会绑定一个初始任务</strong>，启动 Worker 时优先执行，这也是造成线程池不公平的原因。Worker 继承自 AQS，本身具有锁的特性，采用独占锁模式，state &#x3D; 0 表示未被占用，&gt; 0 表示被占用，&lt; 0 表示初始状态不能被抢锁</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">final</span> <span class="token keyword">class</span> <span class="token class-name">Worker</span> <span class="token keyword">extends</span> <span class="token class-name">AbstractQueuedSynchronizer</span> <span class="token keyword">implements</span> <span class="token class-name">Runnable</span> <span class="token punctuation">{</span>    <span class="token keyword">final</span> Thread thread<span class="token punctuation">;</span><span class="token comment" spellcheck="true">// worker 内部封装的工作线程</span>    Runnable firstTask<span class="token punctuation">;</span><span class="token comment" spellcheck="true">// worker 第一个执行的任务，普通的 Runnable 实现类或者是 FutureTask</span>    <span class="token keyword">volatile</span> <span class="token keyword">long</span> completedTasks<span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 记录当前 worker 所完成任务数量</span>        <span class="token comment" spellcheck="true">// 构造方法</span>    <span class="token function">Worker</span><span class="token punctuation">(</span>Runnable firstTask<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 设置AQS独占模式为初始化中状态，这个状态不能被抢占锁</span>           <span class="token function">setState</span><span class="token punctuation">(</span><span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// firstTask不为空时，当worker启动后，内部线程会优先执行firstTask，执行完后会到queue中去获取下个任务</span>        <span class="token keyword">this</span><span class="token punctuation">.</span>firstTask <span class="token operator">=</span> firstTask<span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 使用线程工厂创建一个线程，并且【将当前worker指定为Runnable】，所以thread启动时会调用 worker.run()</span>        <span class="token keyword">this</span><span class="token punctuation">.</span>thread <span class="token operator">=</span> <span class="token function">getThreadFactory</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">newThread</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">// 【不可重入锁】</span>    <span class="token keyword">protected</span> <span class="token keyword">boolean</span> <span class="token function">tryAcquire</span><span class="token punctuation">(</span><span class="token keyword">int</span> unused<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">compareAndSetState</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token function">setExclusiveOwnerThread</span><span class="token punctuation">(</span>Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> Thread <span class="token function">newThread</span><span class="token punctuation">(</span>Runnable r<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 将当前 worker 指定为 thread 的执行方法，线程调用 start 会调用 r.run()</span>    Thread t <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span>group<span class="token punctuation">,</span> r<span class="token punctuation">,</span> namePrefix <span class="token operator">+</span> threadNumber<span class="token punctuation">.</span><span class="token function">getAndIncrement</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>t<span class="token punctuation">.</span><span class="token function">isDaemon</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>        t<span class="token punctuation">.</span><span class="token function">setDaemon</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>t<span class="token punctuation">.</span><span class="token function">getPriority</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">!=</span> Thread<span class="token punctuation">.</span>NORM_PRIORITY<span class="token punctuation">)</span>        t<span class="token punctuation">.</span><span class="token function">setPriority</span><span class="token punctuation">(</span>Thread<span class="token punctuation">.</span>NORM_PRIORITY<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> t<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>拒绝策略相关的内部类</p></li></ul><h3 id="成员方法-2"><a href="#成员方法-2" class="headerlink" title="成员方法"></a>成员方法</h3><h4 id="提交方法-1"><a href="#提交方法-1" class="headerlink" title="提交方法"></a>提交方法</h4><ul><li><p>AbstractExecutorService#submit()：提交任务，<strong>把 Runnable 或 Callable 任务封装成 FutureTask 执行</strong>，可以通过方法返回的任务对象，调用 get 阻塞获取任务执行的结果或者异常，源码分析在笔记的 Future 部分</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> Future<span class="token operator">&lt;</span><span class="token operator">?</span><span class="token operator">></span> <span class="token function">submit</span><span class="token punctuation">(</span>Runnable task<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 空指针异常</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>task <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">NullPointerException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 把 Runnable 封装成未来任务对象，执行结果是 null，也可以通过参数指定 FutureTask#get 返回数据</span>    RunnableFuture<span class="token operator">&lt;</span>Void<span class="token operator">></span> ftask <span class="token operator">=</span> <span class="token function">newTaskFor</span><span class="token punctuation">(</span>task<span class="token punctuation">,</span> null<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 执行方法</span>    <span class="token function">execute</span><span class="token punctuation">(</span>ftask<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> ftask<span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token keyword">public</span> <span class="token operator">&lt;</span>T<span class="token operator">></span> Future<span class="token operator">&lt;</span>T<span class="token operator">></span> <span class="token function">submit</span><span class="token punctuation">(</span>Callable<span class="token operator">&lt;</span>T<span class="token operator">></span> task<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>task <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">NullPointerException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 把 Callable 封装成未来任务对象</span>    RunnableFuture<span class="token operator">&lt;</span>T<span class="token operator">></span> ftask <span class="token operator">=</span> <span class="token function">newTaskFor</span><span class="token punctuation">(</span>task<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 执行方法</span>    <span class="token function">execute</span><span class="token punctuation">(</span>ftask<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 返回未来任务对象，用来获取返回值</span>    <span class="token keyword">return</span> ftask<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">protected</span> <span class="token operator">&lt;</span>T<span class="token operator">></span> RunnableFuture<span class="token operator">&lt;</span>T<span class="token operator">></span> <span class="token function">newTaskFor</span><span class="token punctuation">(</span>Runnable runnable<span class="token punctuation">,</span> T value<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// Runnable 封装成 FutureTask，【指定返回值】</span>    <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">FutureTask</span><span class="token operator">&lt;</span>T<span class="token operator">></span><span class="token punctuation">(</span>runnable<span class="token punctuation">,</span> value<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token keyword">protected</span> <span class="token operator">&lt;</span>T<span class="token operator">></span> RunnableFuture<span class="token operator">&lt;</span>T<span class="token operator">></span> <span class="token function">newTaskFor</span><span class="token punctuation">(</span>Callable<span class="token operator">&lt;</span>T<span class="token operator">></span> callable<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// Callable 直接封装成 FutureTask</span>    <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">FutureTask</span><span class="token operator">&lt;</span>T<span class="token operator">></span><span class="token punctuation">(</span>callable<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>execute()：执行任务，<strong>但是没有返回值，没办法获取任务执行结果</strong>，出现异常会直接抛出任务执行时的异常。根据线程池中的线程数，选择添加任务时的处理方式</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// command 可以是普通的 Runnable 实现类，也可以是 FutureTask，不能是 Callable</span><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">execute</span><span class="token punctuation">(</span>Runnable command<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 非空判断</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>command <span class="token operator">==</span> null<span class="token punctuation">)</span>        <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">NullPointerException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token comment" spellcheck="true">// 获取 ctl 最新值赋值给 c，ctl 高 3 位表示线程池状态，低位表示当前线程池线程数量。</span>    <span class="token keyword">int</span> c <span class="token operator">=</span> ctl<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 【1】当前线程数量小于核心线程数，此次提交任务直接创建一个新的 worker，线程池中多了一个新的线程</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">workerCountOf</span><span class="token punctuation">(</span>c<span class="token punctuation">)</span> <span class="token operator">&lt;</span> corePoolSize<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// addWorker 为创建线程的过程，会创建 worker 对象并且将 command 作为 firstTask，优先执行</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">addWorker</span><span class="token punctuation">(</span>command<span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">)</span>            <span class="token keyword">return</span><span class="token punctuation">;</span>                <span class="token comment" spellcheck="true">// 执行到这条语句，说明 addWorker 一定是失败的，存在并发现象或者线程池状态被改变，重新获取状态</span>        <span class="token comment" spellcheck="true">// SHUTDOWN 状态下也有可能创建成功，前提 firstTask == null 而且当前 queue 不为空（特殊情况）</span>        c <span class="token operator">=</span> ctl<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">// 【2】执行到这说明当前线程数量已经达到核心线程数量 或者 addWorker 失败</span>    <span class="token comment" spellcheck="true">// 判断当前线程池是否处于running状态，成立就尝试将 task 放入到 workQueue 中</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">isRunning</span><span class="token punctuation">(</span>c<span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> workQueue<span class="token punctuation">.</span><span class="token function">offer</span><span class="token punctuation">(</span>command<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">int</span> recheck <span class="token operator">=</span> ctl<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 条件一成立说明线程池状态被外部线程给修改了，可能是执行了 shutdown() 方法，该状态不能接收新提交的任务</span>        <span class="token comment" spellcheck="true">// 所以要把刚提交的任务删除，删除成功说明提交之后线程池中的线程还未消费（处理）该任务</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">isRunning</span><span class="token punctuation">(</span>recheck<span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> <span class="token function">remove</span><span class="token punctuation">(</span>command<span class="token punctuation">)</span><span class="token punctuation">)</span>            <span class="token comment" spellcheck="true">// 任务出队成功，走拒绝策略</span>            <span class="token function">reject</span><span class="token punctuation">(</span>command<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 执行到这说明线程池是 running 状态，获取线程池中的线程数量，判断是否是 0</span>        <span class="token comment" spellcheck="true">// 【担保机制】，保证线程池在 running 状态下，最起码得有一个线程在工作</span>        <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">workerCountOf</span><span class="token punctuation">(</span>recheck<span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span>            <span class="token function">addWorker</span><span class="token punctuation">(</span>null<span class="token punctuation">,</span> <span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">// 【3】offer失败说明queue满了</span>    <span class="token comment" spellcheck="true">// 如果线程数量尚未达到 maximumPoolSize，会创建非核心 worker 线程直接执行 command，【这也是不公平的原因】</span>    <span class="token comment" spellcheck="true">// 如果当前线程数量达到 maximumPoolSiz，这里 addWorker 也会失败，走拒绝策略</span>    <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">addWorker</span><span class="token punctuation">(</span>command<span class="token punctuation">,</span> <span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">)</span>        <span class="token function">reject</span><span class="token punctuation">(</span>command<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li></ul><h4 id="添加线程"><a href="#添加线程" class="headerlink" title="添加线程"></a>添加线程</h4><ul><li><p>prestartAllCoreThreads()：<strong>提前预热</strong>，创建所有的核心线程</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">prestartAllCoreThreads</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">int</span> n <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>    <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token function">addWorker</span><span class="token punctuation">(</span>null<span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">)</span>        <span class="token operator">++</span>n<span class="token punctuation">;</span>    <span class="token keyword">return</span> n<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>addWorker()：<strong>添加线程到线程池</strong>，返回 true 表示创建 Worker 成功，且线程启动。首先判断线程池是否允许添加线程，允许就让线程数量 + 1，然后去创建 Worker 加入线程池</p><p>注意：SHUTDOWN 状态也能添加线程，但是要求新加的 Woker 没有 firstTask，而且当前 queue 不为空，所以创建一个线程来帮助线程池执行队列中的任务</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// core == true 表示采用核心线程数量限制，false 表示采用 maximumPoolSize</span><span class="token keyword">private</span> <span class="token keyword">boolean</span> <span class="token function">addWorker</span><span class="token punctuation">(</span>Runnable firstTask<span class="token punctuation">,</span> <span class="token keyword">boolean</span> core<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 自旋【判断当前线程池状态是否允许创建线程】，允许就设置线程数量 + 1</span>    retry<span class="token operator">:</span>    <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token punctuation">;</span><span class="token punctuation">;</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 获取 ctl 的值</span>        <span class="token keyword">int</span> c <span class="token operator">=</span> ctl<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 获取当前线程池运行状态</span>        <span class="token keyword">int</span> rs <span class="token operator">=</span> <span class="token function">runStateOf</span><span class="token punctuation">(</span>c<span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token comment" spellcheck="true">// 判断当前线程池状态【是否允许添加线程】</span>                <span class="token comment" spellcheck="true">// 当前线程池是 SHUTDOWN 状态，但是队列里面还有任务尚未处理完，需要处理完 queue 中的任务</span>        <span class="token comment" spellcheck="true">// 【不允许再提交新的 task，所以 firstTask 为空，但是可以继续添加 worker】</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>rs <span class="token operator">>=</span> SHUTDOWN <span class="token operator">&amp;&amp;</span> <span class="token operator">!</span><span class="token punctuation">(</span>rs <span class="token operator">==</span> SHUTDOWN <span class="token operator">&amp;&amp;</span> firstTask <span class="token operator">==</span> null <span class="token operator">&amp;&amp;</span> <span class="token operator">!</span>workQueue<span class="token punctuation">.</span><span class="token function">isEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span>            <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span>        <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token punctuation">;</span><span class="token punctuation">;</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 获取线程池中线程数量</span>            <span class="token keyword">int</span> wc <span class="token operator">=</span> <span class="token function">workerCountOf</span><span class="token punctuation">(</span>c<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">// 条件一一般不成立，CAPACITY是5亿多，根据 core 判断使用哪个大小限制线程数量，超过了返回 false</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span>wc <span class="token operator">>=</span> CAPACITY <span class="token operator">||</span> wc <span class="token operator">>=</span> <span class="token punctuation">(</span>core <span class="token operator">?</span> corePoolSize <span class="token operator">:</span> maximumPoolSize<span class="token punctuation">)</span><span class="token punctuation">)</span>                <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">// 记录线程数量已经加 1，类比于申请到了一块令牌，条件失败说明其他线程修改了数量</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">compareAndIncrementWorkerCount</span><span class="token punctuation">(</span>c<span class="token punctuation">)</span><span class="token punctuation">)</span>                <span class="token comment" spellcheck="true">// 申请成功，跳出了 retry 这个 for 自旋</span>                <span class="token keyword">break</span> retry<span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">// CAS 失败，没有成功的申请到令牌</span>            c <span class="token operator">=</span> ctl<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">// 判断当前线程池状态是否发生过变化，被其他线程修改了，可能其他线程调用了 shutdown() 方法</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">runStateOf</span><span class="token punctuation">(</span>c<span class="token punctuation">)</span> <span class="token operator">!=</span> rs<span class="token punctuation">)</span>                <span class="token comment" spellcheck="true">// 返回外层循环检查是否能创建线程，在 if 语句中返回 false</span>                <span class="token keyword">continue</span> retry<span class="token punctuation">;</span>                   <span class="token punctuation">}</span>    <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">//【令牌申请成功，开始创建线程】</span>        <span class="token comment" spellcheck="true">// 运行标记，表示创建的 worker 是否已经启动，false未启动  true启动</span>    <span class="token keyword">boolean</span> workerStarted <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 添加标记，表示创建的 worker 是否添加到池子中了，默认false未添加，true是添加。</span>    <span class="token keyword">boolean</span> workerAdded <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>    Worker w <span class="token operator">=</span> null<span class="token punctuation">;</span>    <span class="token keyword">try</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 【创建 Worker，底层通过线程工厂 newThread 方法创建执行线程，指定了首先执行的任务】</span>        w <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Worker</span><span class="token punctuation">(</span>firstTask<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 将新创建的 worker 节点中的线程赋值给 t</span>        <span class="token keyword">final</span> Thread t <span class="token operator">=</span> w<span class="token punctuation">.</span>thread<span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 这里的判断为了防止 程序员自定义的 ThreadFactory 实现类有 bug，创造不出线程</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>t <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token keyword">final</span> ReentrantLock mainLock <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>mainLock<span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">// 加互斥锁，要添加 worker 了</span>            mainLock<span class="token punctuation">.</span><span class="token function">lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">try</span> <span class="token punctuation">{</span>                <span class="token comment" spellcheck="true">// 获取最新线程池运行状态保存到 rs</span>                <span class="token keyword">int</span> rs <span class="token operator">=</span> <span class="token function">runStateOf</span><span class="token punctuation">(</span>ctl<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token comment" spellcheck="true">// 判断线程池是否为RUNNING状态，不是再【判断当前是否为SHUTDOWN状态且firstTask为空，特殊情况】</span>                <span class="token keyword">if</span> <span class="token punctuation">(</span>rs <span class="token operator">&lt;</span> SHUTDOWN <span class="token operator">||</span> <span class="token punctuation">(</span>rs <span class="token operator">==</span> SHUTDOWN <span class="token operator">&amp;&amp;</span> firstTask <span class="token operator">==</span> null<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                    <span class="token comment" spellcheck="true">// 当线程start后，线程isAlive会返回true，这里还没开始启动线程，如果被启动了就需要报错</span>                    <span class="token keyword">if</span> <span class="token punctuation">(</span>t<span class="token punctuation">.</span><span class="token function">isAlive</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>                        <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">IllegalThreadStateException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                                        <span class="token comment" spellcheck="true">//【将新建的 Worker 添加到线程池中】</span>                    workers<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>w<span class="token punctuation">)</span><span class="token punctuation">;</span>                    <span class="token keyword">int</span> s <span class="token operator">=</span> workers<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                    <span class="token comment" spellcheck="true">// 当前池中的线程数量是一个新高，更新 largestPoolSize</span>                    <span class="token keyword">if</span> <span class="token punctuation">(</span>s <span class="token operator">></span> largestPoolSize<span class="token punctuation">)</span>                        largestPoolSize <span class="token operator">=</span> s<span class="token punctuation">;</span>                    <span class="token comment" spellcheck="true">// 添加标记置为 true</span>                    workerAdded <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>                <span class="token punctuation">}</span>            <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>                <span class="token comment" spellcheck="true">// 解锁啊</span>                mainLock<span class="token punctuation">.</span><span class="token function">unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>            <span class="token comment" spellcheck="true">// 添加成功就【启动线程执行任务】</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span>workerAdded<span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token comment" spellcheck="true">// Thread 类中持有 Runnable 任务对象，调用的是 Runnable 的 run ，也就是 FutureTask</span>                t<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token comment" spellcheck="true">// 运行标记置为 true</span>                workerStarted <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 如果启动线程失败，做清理工作</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span> workerStarted<span class="token punctuation">)</span>            <span class="token function">addWorkerFailed</span><span class="token punctuation">(</span>w<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">// 返回新创建的线程是否启动</span>    <span class="token keyword">return</span> workerStarted<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>addWorkerFailed()：清理任务</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">addWorkerFailed</span><span class="token punctuation">(</span>Worker w<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">final</span> ReentrantLock mainLock <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>mainLock<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 持有线程池全局锁，因为操作的是线程池相关的东西</span>    mainLock<span class="token punctuation">.</span><span class="token function">lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">try</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">//条件成立需要将 worker 在 workers 中清理出去。</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>w <span class="token operator">!=</span> null<span class="token punctuation">)</span>            workers<span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span>w<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 将线程池计数 -1，相当于归还令牌。</span>        <span class="token function">decrementWorkerCount</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 尝试停止线程池</span>        <span class="token function">tryTerminate</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">//释放线程池全局锁。</span>        mainLock<span class="token punctuation">.</span><span class="token function">unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li></ul><h4 id="运行方法"><a href="#运行方法" class="headerlink" title="运行方法"></a>运行方法</h4><ul><li><p>Worker#run：Worker 实现了 Runnable 接口，当线程启动时，会调用 Worker 的 run() 方法</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">run</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// ThreadPoolExecutor#runWorker()</span>    <span class="token function">runWorker</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>runWorker()：线程启动就要<strong>执行任务</strong>，会一直 while 循环获取任务并执行</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">final</span> <span class="token keyword">void</span> <span class="token function">runWorker</span><span class="token punctuation">(</span>Worker w<span class="token punctuation">)</span> <span class="token punctuation">{</span>    Thread wt <span class="token operator">=</span> Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 获取 worker 的 firstTask</span>    Runnable task <span class="token operator">=</span> w<span class="token punctuation">.</span>firstTask<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 引用置空，【防止复用该线程时重复执行该任务】</span>    w<span class="token punctuation">.</span>firstTask <span class="token operator">=</span> null<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 初始化 worker 时设置 state = -1，表示不允许抢占锁</span>    <span class="token comment" spellcheck="true">// 这里需要设置 state = 0 和 exclusiveOwnerThread = null，开始独占模式抢锁</span>    w<span class="token punctuation">.</span><span class="token function">unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// true 表示发生异常退出，false 表示正常退出。</span>    <span class="token keyword">boolean</span> completedAbruptly <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>    <span class="token keyword">try</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// firstTask 不是 null 就直接运行，否则去 queue 中获取任务</span>        <span class="token comment" spellcheck="true">// 【getTask 如果是阻塞获取任务，会一直阻塞在take方法，直到获取任务，不会走返回null的逻辑】</span>        <span class="token keyword">while</span> <span class="token punctuation">(</span>task <span class="token operator">!=</span> null <span class="token operator">||</span> <span class="token punctuation">(</span>task <span class="token operator">=</span> <span class="token function">getTask</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// worker 加锁，shutdown 时会判断当前 worker 状态，【根据独占锁状态判断是否空闲】</span>            w<span class="token punctuation">.</span><span class="token function">lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                        <span class="token comment" spellcheck="true">// 说明线程池状态大于 STOP，目前处于 STOP/TIDYING/TERMINATION，此时给线程一个中断信号</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token function">runStateAtLeast</span><span class="token punctuation">(</span>ctl<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> STOP<span class="token punctuation">)</span> <span class="token operator">||</span>                 <span class="token comment" spellcheck="true">// 说明线程处于 RUNNING 或者 SHUTDOWN 状态，清除打断标记</span>                 <span class="token punctuation">(</span>Thread<span class="token punctuation">.</span><span class="token function">interrupted</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> <span class="token function">runStateAtLeast</span><span class="token punctuation">(</span>ctl<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> STOP<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> <span class="token operator">!</span>wt<span class="token punctuation">.</span><span class="token function">isInterrupted</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>                <span class="token comment" spellcheck="true">// 中断线程，设置线程的中断标志位为 true</span>                wt<span class="token punctuation">.</span><span class="token function">interrupt</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">try</span> <span class="token punctuation">{</span>                <span class="token comment" spellcheck="true">// 钩子方法，【任务执行的前置处理】</span>                <span class="token function">beforeExecute</span><span class="token punctuation">(</span>wt<span class="token punctuation">,</span> task<span class="token punctuation">)</span><span class="token punctuation">;</span>                Throwable thrown <span class="token operator">=</span> null<span class="token punctuation">;</span>                <span class="token keyword">try</span> <span class="token punctuation">{</span>                    <span class="token comment" spellcheck="true">// 【执行任务】</span>                    task<span class="token punctuation">.</span><span class="token function">run</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Exception</span> x<span class="token punctuation">)</span> <span class="token punctuation">{</span>                     <span class="token comment" spellcheck="true">//.....</span>                <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>                    <span class="token comment" spellcheck="true">// 钩子方法，【任务执行的后置处理】</span>                    <span class="token function">afterExecute</span><span class="token punctuation">(</span>task<span class="token punctuation">,</span> thrown<span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token punctuation">}</span>            <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>                task <span class="token operator">=</span> null<span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 将局部变量task置为null，代表任务执行完成</span>                w<span class="token punctuation">.</span>completedTasks<span class="token operator">++</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 更新worker完成任务数量</span>                w<span class="token punctuation">.</span><span class="token function">unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 解锁</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// getTask()方法返回null时会走到这里，表示queue为空并且线程空闲超过保活时间，【当前线程执行退出逻辑】</span>        completedAbruptly <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 正常退出 completedAbruptly = false</span>           <span class="token comment" spellcheck="true">// 异常退出 completedAbruptly = true，【从 task.run() 内部抛出异常】时，跳到这一行</span>        <span class="token function">processWorkerExit</span><span class="token punctuation">(</span>w<span class="token punctuation">,</span> completedAbruptly<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>unlock()：重置锁</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">release</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token comment" spellcheck="true">// 外部不会直接调用这个方法 这个方法是 AQS 内调用的，外部调用 unlock 时触发此方法</span><span class="token keyword">protected</span> <span class="token keyword">boolean</span> <span class="token function">tryRelease</span><span class="token punctuation">(</span><span class="token keyword">int</span> unused<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token function">setExclusiveOwnerThread</span><span class="token punctuation">(</span>null<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 设置持有者为 null</span>    <span class="token function">setState</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 设置 state = 0</span>    <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>getTask()：获取任务，线程空闲时间超过 keepAliveTime 就会被回收，判断的依据是<strong>当前线程阻塞获取任务超过保活时间</strong>，方法返回 null 就代表当前线程要被回收了，返回到 runWorker 执行线程退出逻辑。线程池具有担保机制，对于 RUNNING 状态下的超时回收，要保证线程池中最少有一个线程运行，或者任务阻塞队列已经是空</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> Runnable <span class="token function">getTask</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 超时标记，表示当前线程获取任务是否超时，true 表示已超时</span>    <span class="token keyword">boolean</span> timedOut <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>     <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token punctuation">;</span><span class="token punctuation">;</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">int</span> c <span class="token operator">=</span> ctl<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 获取线程池当前运行状态</span>        <span class="token keyword">int</span> rs <span class="token operator">=</span> <span class="token function">runStateOf</span><span class="token punctuation">(</span>c<span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token comment" spellcheck="true">// 【tryTerminate】打断线程后执行到这，此时线程池状态为STOP或者线程池状态为SHUTDOWN并且队列已经是空</span>        <span class="token comment" spellcheck="true">// 所以下面的 if 条件一定是成立的，可以直接返回 null，线程就应该退出了</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>rs <span class="token operator">>=</span> SHUTDOWN <span class="token operator">&amp;&amp;</span> <span class="token punctuation">(</span>rs <span class="token operator">>=</span> STOP <span class="token operator">||</span> workQueue<span class="token punctuation">.</span><span class="token function">isEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 使用 CAS 自旋的方式让 ctl 值 -1</span>            <span class="token function">decrementWorkerCount</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">return</span> null<span class="token punctuation">;</span>        <span class="token punctuation">}</span>                <span class="token comment" spellcheck="true">// 获取线程池中的线程数量</span>        <span class="token keyword">int</span> wc <span class="token operator">=</span> <span class="token function">workerCountOf</span><span class="token punctuation">(</span>c<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 线程没有明确的区分谁是核心或者非核心线程，是根据当前池中的线程数量判断</span>                <span class="token comment" spellcheck="true">// timed = false 表示当前这个线程 获取task时不支持超时机制的，当前线程会使用 queue.take() 阻塞获取</span>        <span class="token comment" spellcheck="true">// timed = true 表示当前这个线程 获取task时支持超时机制，使用 queue.poll(xxx,xxx) 超时获取</span>        <span class="token comment" spellcheck="true">// 条件一代表允许回收核心线程，那就无所谓了，全部线程都执行超时回收</span>        <span class="token comment" spellcheck="true">// 条件二成立说明线程数量大于核心线程数，当前线程认为是非核心线程，有保活时间，去超时获取任务</span>        <span class="token keyword">boolean</span> timed <span class="token operator">=</span> allowCoreThreadTimeOut <span class="token operator">||</span> wc <span class="token operator">></span> corePoolSize<span class="token punctuation">;</span>                <span class="token comment" spellcheck="true">// 如果线程数量是否超过最大线程数，直接回收</span>        <span class="token comment" spellcheck="true">// 如果当前线程【允许超时回收并且已经超时了】，就应该被回收了，由于【担保机制】还要做判断：</span>        <span class="token comment" spellcheck="true">//   wc > 1 说明线程池还用其他线程，当前线程可以直接回收</span>        <span class="token comment" spellcheck="true">//    workQueue.isEmpty() 前置条件是 wc = 1，【如果当前任务队列也是空了，最后一个线程就可以退出】</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>wc <span class="token operator">></span> maximumPoolSize <span class="token operator">||</span> <span class="token punctuation">(</span>timed <span class="token operator">&amp;&amp;</span> timedOut<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> <span class="token punctuation">(</span>wc <span class="token operator">></span> <span class="token number">1</span> <span class="token operator">||</span> workQueue<span class="token punctuation">.</span><span class="token function">isEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 使用 CAS 机制将 ctl 值 -1 ,减 1 成功的线程，返回 null，代表可以退出</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">compareAndDecrementWorkerCount</span><span class="token punctuation">(</span>c<span class="token punctuation">)</span><span class="token punctuation">)</span>                <span class="token keyword">return</span> null<span class="token punctuation">;</span>            <span class="token keyword">continue</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">try</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 根据当前线程是否需要超时回收，【选择从队列获取任务的方法】是超时获取或者阻塞获取</span>            Runnable r <span class="token operator">=</span> timed <span class="token operator">?</span>                workQueue<span class="token punctuation">.</span><span class="token function">poll</span><span class="token punctuation">(</span>keepAliveTime<span class="token punctuation">,</span> TimeUnit<span class="token punctuation">.</span>NANOSECONDS<span class="token punctuation">)</span> <span class="token operator">:</span> workQueue<span class="token punctuation">.</span><span class="token function">take</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">// 获取到任务返回任务，【阻塞获取会阻塞到获取任务为止】，不会返回 null</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span>r <span class="token operator">!=</span> null<span class="token punctuation">)</span>                <span class="token keyword">return</span> r<span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">// 获取任务为 null 说明超时了，将超时标记设置为 true，下次自旋时返 null</span>            timedOut <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">InterruptedException</span> retry<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 阻塞线程被打断后超时标记置为 false，【说明被打断不算超时】，要继续获取，直到超时或者获取到任务</span>            <span class="token comment" spellcheck="true">// 如果线程池 SHUTDOWN 状态下的打断，会在循环获取任务前判断，返回 null</span>            timedOut <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>processWorkerExit()：<strong>线程退出线程池</strong>，也有担保机制，保证队列中的任务被执行</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// 正常退出 completedAbruptly = false，异常退出为 true</span><span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">processWorkerExit</span><span class="token punctuation">(</span>Worker w<span class="token punctuation">,</span> <span class="token keyword">boolean</span> completedAbruptly<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 条件成立代表当前 worker 是发生异常退出的，task 任务执行过程中向上抛出异常了</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>completedAbruptly<span class="token punctuation">)</span>         <span class="token comment" spellcheck="true">// 从异常时到这里 ctl 一直没有 -1，需要在这里 -1</span>        <span class="token function">decrementWorkerCount</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">final</span> ReentrantLock mainLock <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>mainLock<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 加锁</span>    mainLock<span class="token punctuation">.</span><span class="token function">lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">try</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 将当前 worker 完成的 task 数量，汇总到线程池的 completedTaskCount</span>        completedTaskCount <span class="token operator">+=</span> w<span class="token punctuation">.</span>completedTasks<span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 将 worker 从线程池中移除</span>        workers<span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span>w<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>        mainLock<span class="token punctuation">.</span><span class="token function">unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 解锁</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">// 尝试停止线程池，唤醒下一个线程</span>    <span class="token function">tryTerminate</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">int</span> c <span class="token operator">=</span> ctl<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 线程池不是停止状态就应该有线程运行【担保机制】</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">runStateLessThan</span><span class="token punctuation">(</span>c<span class="token punctuation">,</span> STOP<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 正常退出的逻辑，是对空闲线程回收，不是执行出错</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>completedAbruptly<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 根据是否回收核心线程确定【线程池中的线程数量最小值】</span>            <span class="token keyword">int</span> min <span class="token operator">=</span> allowCoreThreadTimeOut <span class="token operator">?</span> <span class="token number">0</span> <span class="token operator">:</span> corePoolSize<span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">// 最小值为 0，但是线程队列不为空，需要一个线程来完成任务担保机制</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span>min <span class="token operator">==</span> <span class="token number">0</span> <span class="token operator">&amp;&amp;</span> <span class="token operator">!</span>workQueue<span class="token punctuation">.</span><span class="token function">isEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>                min <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">// 线程池中的线程数量大于最小值可以直接返回</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">workerCountOf</span><span class="token punctuation">(</span>c<span class="token punctuation">)</span> <span class="token operator">>=</span> min<span class="token punctuation">)</span>                <span class="token keyword">return</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 执行 task 时发生异常，有个线程因为异常终止了，需要添加</span>        <span class="token comment" spellcheck="true">// 或者线程池中的数量小于最小值，这里要创建一个新 worker 加进线程池</span>        <span class="token function">addWorker</span><span class="token punctuation">(</span>null<span class="token punctuation">,</span> <span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li></ul><h4 id="停止方法"><a href="#停止方法" class="headerlink" title="停止方法"></a>停止方法</h4><ul><li><p>shutdown()：停止线程池</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">shutdown</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">final</span> ReentrantLock mainLock <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>mainLock<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 获取线程池全局锁</span>    mainLock<span class="token punctuation">.</span><span class="token function">lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">try</span> <span class="token punctuation">{</span>        <span class="token function">checkShutdownAccess</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 设置线程池状态为 SHUTDOWN，如果线程池状态大于 SHUTDOWN，就不会设置直接返回</span>        <span class="token function">advanceRunState</span><span class="token punctuation">(</span>SHUTDOWN<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 中断空闲线程</span>        <span class="token function">interruptIdleWorkers</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 空方法，子类可以扩展</span>        <span class="token function">onShutdown</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>     <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 释放线程池全局锁</span>        mainLock<span class="token punctuation">.</span><span class="token function">unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token function">tryTerminate</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>interruptIdleWorkers()：shutdown 方法会<strong>中断所有空闲线程</strong>，根据是否可以获取 AQS 独占锁判断是否处于工作状态。线程之所以空闲是因为阻塞队列没有任务，不会中断正在运行的线程，所以 shutdown 方法会让所有的任务执行完毕</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// onlyOne == true 说明只中断一个线程 ，false 则中断所有线程</span><span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">interruptIdleWorkers</span><span class="token punctuation">(</span><span class="token keyword">boolean</span> onlyOne<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">final</span> ReentrantLock mainLock <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>mainLock<span class="token punctuation">;</span>    <span class="token operator">/</span> <span class="token operator">/</span>持有全局锁    mainLock<span class="token punctuation">.</span><span class="token function">lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">try</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 遍历所有 worker</span>        <span class="token keyword">for</span> <span class="token punctuation">(</span>Worker w <span class="token operator">:</span> workers<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 获取当前 worker 的线程</span>            Thread t <span class="token operator">=</span> w<span class="token punctuation">.</span>thread<span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">// 条件一成立：说明当前迭代的这个线程尚未中断</span>            <span class="token comment" spellcheck="true">// 条件二成立：说明【当前worker处于空闲状态】，阻塞在poll或者take，因为worker执行task时是要加锁的</span>            <span class="token comment" spellcheck="true">//           每个worker有一个独占锁，w.tryLock()尝试加锁，加锁成功返回 true</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>t<span class="token punctuation">.</span><span class="token function">isInterrupted</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> w<span class="token punctuation">.</span><span class="token function">tryLock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token keyword">try</span> <span class="token punctuation">{</span>                    <span class="token comment" spellcheck="true">// 中断线程，处于 queue 阻塞的线程会被唤醒，进入下一次自旋，返回 null，执行退出相逻辑</span>                    t<span class="token punctuation">.</span><span class="token function">interrupt</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">SecurityException</span> ignore<span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>                    <span class="token comment" spellcheck="true">// 释放worker的独占锁</span>                    w<span class="token punctuation">.</span><span class="token function">unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token punctuation">}</span>            <span class="token punctuation">}</span>            <span class="token comment" spellcheck="true">// false，代表中断所有的线程</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span>onlyOne<span class="token punctuation">)</span>                <span class="token keyword">break</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 释放全局锁</span>        mainLock<span class="token punctuation">.</span><span class="token function">unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>shutdownNow()：直接关闭线程池，不会等待任务执行完成</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> List<span class="token operator">&lt;</span>Runnable<span class="token operator">></span> <span class="token function">shutdownNow</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 返回值引用</span>    List<span class="token operator">&lt;</span>Runnable<span class="token operator">></span> tasks<span class="token punctuation">;</span>    <span class="token keyword">final</span> ReentrantLock mainLock <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>mainLock<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 获取线程池全局锁</span>    mainLock<span class="token punctuation">.</span><span class="token function">lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">try</span> <span class="token punctuation">{</span>        <span class="token function">checkShutdownAccess</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 设置线程池状态为STOP</span>        <span class="token function">advanceRunState</span><span class="token punctuation">(</span>STOP<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 中断线程池中【所有线程】</span>        <span class="token function">interruptWorkers</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 从阻塞队列中导出未处理的task</span>        tasks <span class="token operator">=</span> <span class="token function">drainQueue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>        mainLock<span class="token punctuation">.</span><span class="token function">unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token function">tryTerminate</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 返回当前任务队列中 未处理的任务。</span>    <span class="token keyword">return</span> tasks<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>tryTerminate()：设置为 TERMINATED 状态 if either (SHUTDOWN and pool and queue empty) or (STOP and pool empty)</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">final</span> <span class="token keyword">void</span> <span class="token function">tryTerminate</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token punctuation">;</span><span class="token punctuation">;</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 获取 ctl 的值</span>        <span class="token keyword">int</span> c <span class="token operator">=</span> ctl<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 线程池正常，或者有其他线程执行了状态转换的方法，当前线程直接返回</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">isRunning</span><span class="token punctuation">(</span>c<span class="token punctuation">)</span> <span class="token operator">||</span> <span class="token function">runStateAtLeast</span><span class="token punctuation">(</span>c<span class="token punctuation">,</span> TIDYING<span class="token punctuation">)</span> <span class="token operator">||</span>            <span class="token comment" spellcheck="true">// 线程池是 SHUTDOWN 并且任务队列不是空，需要去处理队列中的任务</span>            <span class="token punctuation">(</span><span class="token function">runStateOf</span><span class="token punctuation">(</span>c<span class="token punctuation">)</span> <span class="token operator">==</span> SHUTDOWN <span class="token operator">&amp;&amp;</span> <span class="token operator">!</span> workQueue<span class="token punctuation">.</span><span class="token function">isEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span>            <span class="token keyword">return</span><span class="token punctuation">;</span>                <span class="token comment" spellcheck="true">// 执行到这里说明线程池状态为 STOP 或者线程池状态为 SHUTDOWN 并且队列已经是空</span>        <span class="token comment" spellcheck="true">// 判断线程池中线程的数量</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">workerCountOf</span><span class="token punctuation">(</span>c<span class="token punctuation">)</span> <span class="token operator">!=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 【中断一个空闲线程】，在 queue.take() | queue.poll() 阻塞空闲</span>            <span class="token comment" spellcheck="true">// 唤醒后的线程会在getTask()方法返回null，</span>            <span class="token comment" spellcheck="true">// 执行 processWorkerExit 退出逻辑时会再次调用 tryTerminate() 唤醒下一个空闲线程</span>            <span class="token function">interruptIdleWorkers</span><span class="token punctuation">(</span>ONLY_ONE<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">return</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 池中的线程数量为 0 来到这里</span>        <span class="token keyword">final</span> ReentrantLock mainLock <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>mainLock<span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 加全局锁</span>        mainLock<span class="token punctuation">.</span><span class="token function">lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">try</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 设置线程池状态为 TIDYING 状态，线程数量为 0</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span>ctl<span class="token punctuation">.</span><span class="token function">compareAndSet</span><span class="token punctuation">(</span>c<span class="token punctuation">,</span> <span class="token function">ctlOf</span><span class="token punctuation">(</span>TIDYING<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token keyword">try</span> <span class="token punctuation">{</span>                    <span class="token comment" spellcheck="true">// 结束线程池</span>                    <span class="token function">terminated</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>                    <span class="token comment" spellcheck="true">// 设置线程池状态为TERMINATED状态。</span>                    ctl<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span><span class="token function">ctlOf</span><span class="token punctuation">(</span>TERMINATED<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                    <span class="token comment" spellcheck="true">// 【唤醒所有调用 awaitTermination() 方法的线程】</span>                    termination<span class="token punctuation">.</span><span class="token function">signalAll</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token punctuation">}</span>                <span class="token keyword">return</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 释放线程池全局锁</span>            mainLock<span class="token punctuation">.</span><span class="token function">unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li></ul><h3 id="Future"><a href="#Future" class="headerlink" title="Future"></a>Future</h3><h4 id="线程使用"><a href="#线程使用" class="headerlink" title="线程使用"></a>线程使用</h4><p><img src="https://img.jwt1399.top/img/202301031939074.png" alt="img"></p><p>FutureTask 未来任务对象，继承 Runnable、Future 接口，用于包装 Callable 对象，实现任务的提交</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token keyword">throws</span> ExecutionException<span class="token punctuation">,</span> InterruptedException <span class="token punctuation">{</span>    FutureTask<span class="token operator">&lt;</span>String<span class="token operator">></span> task <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">FutureTask</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Callable</span><span class="token operator">&lt;</span>String<span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token annotation punctuation">@Override</span>        <span class="token keyword">public</span> String <span class="token function">call</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">throws</span> Exception <span class="token punctuation">{</span>            <span class="token keyword">return</span> <span class="token string">"Hello World"</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span>task<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//启动线程</span>    String msg <span class="token operator">=</span> task<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//获取返回任务数据</span>    System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>msg<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>构造方法：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token function">FutureTask</span><span class="token punctuation">(</span>Callable<span class="token operator">&lt;</span>V<span class="token operator">></span> callable<span class="token punctuation">)</span><span class="token punctuation">{</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>callable <span class="token operator">=</span> callable<span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 属性注入</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>state <span class="token operator">=</span> NEW<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 任务状态设置为 new</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token function">FutureTask</span><span class="token punctuation">(</span>Runnable runnable<span class="token punctuation">,</span> V result<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 适配器模式</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>callable <span class="token operator">=</span> Executors<span class="token punctuation">.</span><span class="token function">callable</span><span class="token punctuation">(</span>runnable<span class="token punctuation">,</span> result<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>state <span class="token operator">=</span> NEW<span class="token punctuation">;</span>       <span class="token punctuation">}</span><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token operator">&lt;</span>T<span class="token operator">></span> Callable<span class="token operator">&lt;</span>T<span class="token operator">></span> <span class="token function">callable</span><span class="token punctuation">(</span>Runnable task<span class="token punctuation">,</span> T result<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>task <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">NullPointerException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 使用装饰者模式将 runnable 转换成 callable 接口，外部线程通过 get 获取</span>    <span class="token comment" spellcheck="true">// 当前任务执行结果时，结果可能为 null 也可能为传进来的值，【传进来什么返回什么】</span>    <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">RunnableAdapter</span><span class="token operator">&lt;</span>T<span class="token operator">></span><span class="token punctuation">(</span>task<span class="token punctuation">,</span> result<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">class</span> <span class="token class-name">RunnableAdapter</span><span class="token operator">&lt;</span>T<span class="token operator">></span> <span class="token keyword">implements</span> <span class="token class-name">Callable</span><span class="token operator">&lt;</span>T<span class="token operator">></span> <span class="token punctuation">{</span>    <span class="token keyword">final</span> Runnable task<span class="token punctuation">;</span>    <span class="token keyword">final</span> T result<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 构造方法</span>    <span class="token function">RunnableAdapter</span><span class="token punctuation">(</span>Runnable task<span class="token punctuation">,</span> T result<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">this</span><span class="token punctuation">.</span>task <span class="token operator">=</span> task<span class="token punctuation">;</span>        <span class="token keyword">this</span><span class="token punctuation">.</span>result <span class="token operator">=</span> result<span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">public</span> T <span class="token function">call</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 实则调用 Runnable#run 方法</span>        task<span class="token punctuation">.</span><span class="token function">run</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 返回值为构造 FutureTask 对象时传入的返回值或者是 null</span>        <span class="token keyword">return</span> result<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><hr><h4 id="成员属性-2"><a href="#成员属性-2" class="headerlink" title="成员属性"></a>成员属性</h4><p>FutureTask 类的成员属性：</p><ul><li><p>任务状态：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// 表示当前task状态</span><span class="token keyword">private</span> <span class="token keyword">volatile</span> <span class="token keyword">int</span> state<span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 当前任务尚未执行</span><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> NEW          <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 当前任务正在结束，尚未完全结束，一种临界状态</span><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> COMPLETING   <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 当前任务正常结束</span><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> NORMAL       <span class="token operator">=</span> <span class="token number">2</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 当前任务执行过程中发生了异常，内部封装的 callable.run() 向上抛出异常了</span><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> EXCEPTIONAL  <span class="token operator">=</span> <span class="token number">3</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 当前任务被取消</span><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> CANCELLED    <span class="token operator">=</span> <span class="token number">4</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 当前任务中断中</span><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> INTERRUPTING <span class="token operator">=</span> <span class="token number">5</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 当前任务已中断</span><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> INTERRUPTED  <span class="token operator">=</span> <span class="token number">6</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>任务对象：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> Callable<span class="token operator">&lt;</span>V<span class="token operator">></span> callable<span class="token punctuation">;</span><span class="token comment" spellcheck="true">// Runnable 使用装饰者模式伪装成 Callable</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre></li><li><p><strong>存储任务执行的结果</strong>，这是 run 方法返回值是 void 也可以获取到执行结果的原因：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// 正常情况下：任务正常执行结束，outcome 保存执行结果，callable 返回值</span><span class="token comment" spellcheck="true">// 非正常情况：callable 向上抛出异常，outcome 保存异常</span><span class="token keyword">private</span> Object outcome<span class="token punctuation">;</span> <span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre></li><li><p>执行当前任务的线程对象：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">volatile</span> Thread runner<span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 当前任务被线程执行期间，保存当前执行任务的线程对象引用</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre></li><li><p><strong>线程阻塞队列的头节点</strong>：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// 会有很多线程去 get 当前任务的结果，这里使用了一种数据结构头插头取（类似栈）的一个队列来保存所有的 get 线程</span><span class="token keyword">private</span> <span class="token keyword">volatile</span> WaitNode waiters<span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre></li><li><p>内部类：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">class</span> <span class="token class-name">WaitNode</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 单向链表</span>    <span class="token keyword">volatile</span> Thread thread<span class="token punctuation">;</span>    <span class="token keyword">volatile</span> WaitNode next<span class="token punctuation">;</span>    <span class="token function">WaitNode</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> thread <span class="token operator">=</span> Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li></ul><hr><h4 id="成员方法-3"><a href="#成员方法-3" class="headerlink" title="成员方法"></a>成员方法</h4><p>FutureTask 类的成员方法：</p><ul><li><p><strong>FutureTask#run</strong>：任务执行入口</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">run</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">//条件一：成立说明当前 task 已经被执行过了或者被 cancel 了，非 NEW 状态的任务，线程就不需要处理了</span>    <span class="token comment" spellcheck="true">//条件二：线程是 NEW 状态，尝试设置当前任务对象的线程是当前线程，设置失败说明其他线程抢占了该任务，直接返回</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>state <span class="token operator">!=</span> NEW <span class="token operator">||</span>        <span class="token operator">!</span>UNSAFE<span class="token punctuation">.</span><span class="token function">compareAndSwapObject</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> runnerOffset<span class="token punctuation">,</span> null<span class="token punctuation">,</span> Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span>        <span class="token keyword">return</span><span class="token punctuation">;</span>    <span class="token keyword">try</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 执行到这里，当前 task 一定是 NEW 状态，而且【当前线程也抢占 task 成功】</span>        Callable<span class="token operator">&lt;</span>V<span class="token operator">></span> c <span class="token operator">=</span> callable<span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 判断任务是否为空，防止空指针异常；判断 state 状态，防止外部线程在此期间 cancel 掉当前任务</span>        <span class="token comment" spellcheck="true">// 【因为 task 的执行者已经设置为当前线程，所以这里是线程安全的】</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>c <span class="token operator">!=</span> null <span class="token operator">&amp;&amp;</span> state <span class="token operator">==</span> NEW<span class="token punctuation">)</span> <span class="token punctuation">{</span>            V result<span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">// true 表示 callable.run 代码块执行成功 未抛出异常</span>            <span class="token comment" spellcheck="true">// false 表示 callable.run 代码块执行失败 抛出异常</span>            <span class="token keyword">boolean</span> ran<span class="token punctuation">;</span>            <span class="token keyword">try</span> <span class="token punctuation">{</span>                <span class="token comment" spellcheck="true">// 【调用自定义的方法，执行结果赋值给 result】</span>                result <span class="token operator">=</span> c<span class="token punctuation">.</span><span class="token function">call</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token comment" spellcheck="true">// 没有出现异常</span>                ran <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Throwable</span> ex<span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token comment" spellcheck="true">// 出现异常，返回值置空，ran 置为 false</span>                result <span class="token operator">=</span> null<span class="token punctuation">;</span>                ran <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>                <span class="token comment" spellcheck="true">// 设置返回的异常</span>                <span class="token function">setException</span><span class="token punctuation">(</span>ex<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>            <span class="token comment" spellcheck="true">// 代码块执行正常</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span>ran<span class="token punctuation">)</span>                <span class="token comment" spellcheck="true">// 设置返回的结果</span>                <span class="token function">set</span><span class="token punctuation">(</span>result<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 任务执行完成，取消线程的引用，help GC</span>        runner <span class="token operator">=</span> null<span class="token punctuation">;</span>        <span class="token keyword">int</span> s <span class="token operator">=</span> state<span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 判断任务是不是被中断</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>s <span class="token operator">>=</span> INTERRUPTING<span class="token punctuation">)</span>            <span class="token comment" spellcheck="true">// 执行中断处理方法</span>            <span class="token function">handlePossibleCancellationInterrupt</span><span class="token punctuation">(</span>s<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>FutureTask#set：设置正常返回值，首先将任务状态设置为 COMPLETING 状态代表完成中，逻辑执行完设置为 NORMAL 状态代表任务正常执行完成，最后唤醒 get() 阻塞线程</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">protected</span> <span class="token keyword">void</span> <span class="token function">set</span><span class="token punctuation">(</span>V v<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// CAS 方式设置当前任务状态为完成中，设置失败说明其他线程取消了该任务</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>UNSAFE<span class="token punctuation">.</span><span class="token function">compareAndSwapInt</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> stateOffset<span class="token punctuation">,</span> NEW<span class="token punctuation">,</span> COMPLETING<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 【将结果赋值给 outcome】</span>        outcome <span class="token operator">=</span> v<span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 将当前任务状态修改为 NORMAL 正常结束状态。</span>        UNSAFE<span class="token punctuation">.</span><span class="token function">putOrderedInt</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> stateOffset<span class="token punctuation">,</span> NORMAL<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token function">finishCompletion</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>FutureTask#setException：设置异常返回值</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">protected</span> <span class="token keyword">void</span> <span class="token function">setException</span><span class="token punctuation">(</span>Throwable t<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>UNSAFE<span class="token punctuation">.</span><span class="token function">compareAndSwapInt</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> stateOffset<span class="token punctuation">,</span> NEW<span class="token punctuation">,</span> COMPLETING<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 赋值给返回结果，用来向上层抛出来的异常</span>        outcome <span class="token operator">=</span> t<span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 将当前任务的状态 修改为 EXCEPTIONAL</span>        UNSAFE<span class="token punctuation">.</span><span class="token function">putOrderedInt</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> stateOffset<span class="token punctuation">,</span> EXCEPTIONAL<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token function">finishCompletion</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>FutureTask#finishCompletion：<strong>唤醒 get() 阻塞线程</strong></p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">finishCompletion</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 遍历所有的等待的节点，q 指向头节点</span>    <span class="token keyword">for</span> <span class="token punctuation">(</span>WaitNode q<span class="token punctuation">;</span> <span class="token punctuation">(</span>q <span class="token operator">=</span> waiters<span class="token punctuation">)</span> <span class="token operator">!=</span> null<span class="token punctuation">;</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 使用cas设置 waiters 为 null，防止外部线程使用cancel取消当前任务，触发finishCompletion方法重复执行</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>UNSAFE<span class="token punctuation">.</span><span class="token function">compareAndSwapObject</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> waitersOffset<span class="token punctuation">,</span> q<span class="token punctuation">,</span> null<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 自旋</span>            <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token punctuation">;</span><span class="token punctuation">;</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token comment" spellcheck="true">// 获取当前 WaitNode 节点封装的 thread</span>                Thread t <span class="token operator">=</span> q<span class="token punctuation">.</span>thread<span class="token punctuation">;</span>                <span class="token comment" spellcheck="true">// 当前线程不为 null，唤醒当前 get() 等待获取数据的线程</span>                <span class="token keyword">if</span> <span class="token punctuation">(</span>t <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>                    q<span class="token punctuation">.</span>thread <span class="token operator">=</span> null<span class="token punctuation">;</span>                    LockSupport<span class="token punctuation">.</span><span class="token function">unpark</span><span class="token punctuation">(</span>t<span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token punctuation">}</span>                <span class="token comment" spellcheck="true">// 获取当前节点的下一个节点</span>                WaitNode next <span class="token operator">=</span> q<span class="token punctuation">.</span>next<span class="token punctuation">;</span>                <span class="token comment" spellcheck="true">// 当前节点是最后一个节点了</span>                <span class="token keyword">if</span> <span class="token punctuation">(</span>next <span class="token operator">==</span> null<span class="token punctuation">)</span>                    <span class="token keyword">break</span><span class="token punctuation">;</span>                <span class="token comment" spellcheck="true">// 断开链表</span>                q<span class="token punctuation">.</span>next <span class="token operator">=</span> null<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// help gc</span>                q <span class="token operator">=</span> next<span class="token punctuation">;</span>            <span class="token punctuation">}</span>            <span class="token keyword">break</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span>    <span class="token function">done</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    callable <span class="token operator">=</span> null<span class="token punctuation">;</span><span class="token comment" spellcheck="true">// help GC</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>FutureTask#handlePossibleCancellationInterrupt：任务中断处理</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">handlePossibleCancellationInterrupt</span><span class="token punctuation">(</span><span class="token keyword">int</span> s<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>s <span class="token operator">==</span> INTERRUPTING<span class="token punctuation">)</span>        <span class="token comment" spellcheck="true">// 中断状态中</span>        <span class="token keyword">while</span> <span class="token punctuation">(</span>state <span class="token operator">==</span> INTERRUPTING<span class="token punctuation">)</span>            <span class="token comment" spellcheck="true">// 等待中断完成</span>            Thread<span class="token punctuation">.</span><span class="token function">yield</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p><strong>FutureTask#get</strong>：获取任务执行的返回值，执行 run 和 get 的不是同一个线程，一般有多个线程 get，只有一个线程 run</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> V <span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">throws</span> InterruptedException<span class="token punctuation">,</span> ExecutionException <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 获取当前任务状态</span>    <span class="token keyword">int</span> s <span class="token operator">=</span> state<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 条件成立说明任务还没执行完成</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>s <span class="token operator">&lt;=</span> COMPLETING<span class="token punctuation">)</span>        <span class="token comment" spellcheck="true">// 返回 task 当前状态，可能当前线程在里面已经睡了一会</span>        s <span class="token operator">=</span> <span class="token function">awaitDone</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">,</span> 0L<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> <span class="token function">report</span><span class="token punctuation">(</span>s<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>FutureTask#awaitDone：<strong>get 线程封装成 WaitNode 对象进入阻塞队列阻塞等待</strong></p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">int</span> <span class="token function">awaitDone</span><span class="token punctuation">(</span><span class="token keyword">boolean</span> timed<span class="token punctuation">,</span> <span class="token keyword">long</span> nanos<span class="token punctuation">)</span> <span class="token keyword">throws</span> InterruptedException <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 0 不带超时</span>    <span class="token keyword">final</span> <span class="token keyword">long</span> deadline <span class="token operator">=</span> timed <span class="token operator">?</span> System<span class="token punctuation">.</span><span class="token function">nanoTime</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> nanos <span class="token operator">:</span> 0L<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 引用当前线程，封装成 WaitNode 对象</span>    WaitNode q <span class="token operator">=</span> null<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 表示当前线程 waitNode 对象，是否进入阻塞队列</span>    <span class="token keyword">boolean</span> queued <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 【三次自旋开始休眠】</span>    <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token punctuation">;</span><span class="token punctuation">;</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 判断当前 get() 线程是否被打断，打断返回 true，清除打断标记</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>Thread<span class="token punctuation">.</span><span class="token function">interrupted</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 当前线程对应的等待 node 出队，</span>            <span class="token function">removeWaiter</span><span class="token punctuation">(</span>q<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">InterruptedException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 获取任务状态</span>        <span class="token keyword">int</span> s <span class="token operator">=</span> state<span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 条件成立说明当前任务执行完成已经有结果了</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>s <span class="token operator">></span> COMPLETING<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 条件成立说明已经为当前线程创建了 WaitNode，置空 help GC</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span>q <span class="token operator">!=</span> null<span class="token punctuation">)</span>                q<span class="token punctuation">.</span>thread <span class="token operator">=</span> null<span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">// 返回当前的状态</span>            <span class="token keyword">return</span> s<span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 条件成立说明当前任务接近完成状态，这里让当前线程释放一下 cpu ，等待进行下一次抢占 cpu</span>        <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>s <span class="token operator">==</span> COMPLETING<span class="token punctuation">)</span>             Thread<span class="token punctuation">.</span><span class="token function">yield</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 【第一次自旋】，当前线程还未创建 WaitNode 对象，此时为当前线程创建 WaitNode对象</span>        <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>q <span class="token operator">==</span> null<span class="token punctuation">)</span>            q <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">WaitNode</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 【第二次自旋】，当前线程已经创建 WaitNode 对象了，但是node对象还未入队</span>        <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>queued<span class="token punctuation">)</span>            <span class="token comment" spellcheck="true">// waiters 指向队首，让当前 WaitNode 成为新的队首，【头插法】，失败说明其他线程修改了新的队首</span>            queued <span class="token operator">=</span> UNSAFE<span class="token punctuation">.</span><span class="token function">compareAndSwapObject</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> waitersOffset<span class="token punctuation">,</span> q<span class="token punctuation">.</span>next <span class="token operator">=</span> waiters<span class="token punctuation">,</span> q<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 【第三次自旋】，会到这里，或者 else 内</span>        <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>timed<span class="token punctuation">)</span> <span class="token punctuation">{</span>            nanos <span class="token operator">=</span> deadline <span class="token operator">-</span> System<span class="token punctuation">.</span><span class="token function">nanoTime</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span>nanos <span class="token operator">&lt;=</span> 0L<span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token function">removeWaiter</span><span class="token punctuation">(</span>q<span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token keyword">return</span> state<span class="token punctuation">;</span>            <span class="token punctuation">}</span>            <span class="token comment" spellcheck="true">// 阻塞指定的时间</span>            LockSupport<span class="token punctuation">.</span><span class="token function">parkNanos</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> nanos<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 条件成立：说明需要阻塞</span>        <span class="token keyword">else</span>            <span class="token comment" spellcheck="true">// 【当前 get 操作的线程被 park 阻塞】，除非有其它线程将唤醒或者将当前线程中断</span>            LockSupport<span class="token punctuation">.</span><span class="token function">park</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>FutureTask#report：封装运行结果，可以获取 run() 方法中设置的成员变量 outcome，<strong>这是 run 方法的返回值是 void 也可以获取到任务执行的结果的原因</strong></p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> V <span class="token function">report</span><span class="token punctuation">(</span><span class="token keyword">int</span> s<span class="token punctuation">)</span> <span class="token keyword">throws</span> ExecutionException <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 获取执行结果，是在一个 futuretask 对象中的属性，可以直接获取</span>    Object x <span class="token operator">=</span> outcome<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 当前任务状态正常结束</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>s <span class="token operator">==</span> NORMAL<span class="token punctuation">)</span>        <span class="token keyword">return</span> <span class="token punctuation">(</span>V<span class="token punctuation">)</span>x<span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 直接返回 callable 的逻辑结果</span>    <span class="token comment" spellcheck="true">// 当前任务被取消或者中断</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>s <span class="token operator">>=</span> CANCELLED<span class="token punctuation">)</span>        <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">CancellationException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 抛出异常</span>    <span class="token comment" spellcheck="true">// 执行到这里说明自定义的 callable 中的方法有异常，使用 outcome 上层抛出异常</span>    <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">ExecutionException</span><span class="token punctuation">(</span><span class="token punctuation">(</span>Throwable<span class="token punctuation">)</span>x<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>FutureTask#cancel：任务取消，打断正在执行该任务的线程</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">boolean</span> <span class="token function">cancel</span><span class="token punctuation">(</span><span class="token keyword">boolean</span> mayInterruptIfRunning<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 条件一：表示当前任务处于运行中或者处于线程池任务队列中</span>    <span class="token comment" spellcheck="true">// 条件二：表示修改状态，成功可以去执行下面逻辑，否则返回 false 表示 cancel 失败</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token punctuation">(</span>state <span class="token operator">==</span> NEW <span class="token operator">&amp;&amp;</span>          UNSAFE<span class="token punctuation">.</span><span class="token function">compareAndSwapInt</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> stateOffset<span class="token punctuation">,</span> NEW<span class="token punctuation">,</span>                                   mayInterruptIfRunning <span class="token operator">?</span> INTERRUPTING <span class="token operator">:</span> CANCELLED<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span>        <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span>    <span class="token keyword">try</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 如果任务已经被执行，是否允许打断</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>mayInterruptIfRunning<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token keyword">try</span> <span class="token punctuation">{</span>                <span class="token comment" spellcheck="true">// 获取执行当前 FutureTask 的线程</span>                Thread t <span class="token operator">=</span> runner<span class="token punctuation">;</span>                <span class="token keyword">if</span> <span class="token punctuation">(</span>t <span class="token operator">!=</span> null<span class="token punctuation">)</span>                    <span class="token comment" spellcheck="true">// 打断执行的线程</span>                    t<span class="token punctuation">.</span><span class="token function">interrupt</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>                <span class="token comment" spellcheck="true">// 设置任务状态为【中断完成】</span>                UNSAFE<span class="token punctuation">.</span><span class="token function">putOrderedInt</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> stateOffset<span class="token punctuation">,</span> INTERRUPTED<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 唤醒所有 get() 阻塞的线程</span>        <span class="token function">finishCompletion</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li></ul><h2 id="❺任务调度"><a href="#❺任务调度" class="headerlink" title="❺任务调度"></a>❺任务调度</h2><h3 id="Timer"><a href="#Timer" class="headerlink" title="Timer"></a>Timer</h3><p>Timer 实现定时功能，Timer 的优点在于简单易用，但由于所有任务都是由同一个线程来调度，因此所有任务都是串行执行的，同一时间只能有一个任务在执行，前一个任务的延迟或异常都将会影响到之后的任务</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">method1</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    Timer timer <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Timer</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    TimerTask task1 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">TimerTask</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token annotation punctuation">@Override</span>        <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">run</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"task 1"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">//int i = 1 / 0;//任务一的出错会导致任务二无法执行</span>            Thread<span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">2000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span><span class="token punctuation">;</span>    TimerTask task2 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">TimerTask</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token annotation punctuation">@Override</span>        <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">run</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"task 2"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 使用 timer 添加两个任务，希望它们都在 1s 后执行</span>    <span class="token comment" spellcheck="true">// 但由于 timer 内只有一个线程来顺序执行队列中的任务，因此任务1的延时，影响了任务2的执行</span>    timer<span class="token punctuation">.</span><span class="token function">schedule</span><span class="token punctuation">(</span>task1<span class="token punctuation">,</span> <span class="token number">1000</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//17:45:56 c.ThreadPool [Timer-0] - task 1</span>    timer<span class="token punctuation">.</span><span class="token function">schedule</span><span class="token punctuation">(</span>task2<span class="token punctuation">,</span> <span class="token number">1000</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//17:45:58 c.ThreadPool [Timer-0] - task 2</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="Scheduled"><a href="#Scheduled" class="headerlink" title="Scheduled"></a>Scheduled</h3><h4 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h4><p>任务调度线程池 ScheduledThreadPoolExecutor 继承 ThreadPoolExecutor：</p><ul><li>使用内部类 ScheduledFutureTask 封装任务</li><li>使用内部类 DelayedWorkQueue 作为线程池队列</li><li>重写 onShutdown 方法去处理 shutdown 后的任务</li><li>提供 decorateTask 方法作为 ScheduledFutureTask 的修饰方法，以便开发者进行扩展</li></ul><p>构造方法：<code>Executors.newScheduledThreadPool(int corePoolSize)</code></p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token function">ScheduledThreadPoolExecutor</span><span class="token punctuation">(</span><span class="token keyword">int</span> corePoolSize<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 最大线程数固定为 Integer.MAX_VALUE，保活时间 keepAliveTime 固定为 0</span>    <span class="token keyword">super</span><span class="token punctuation">(</span>corePoolSize<span class="token punctuation">,</span> Integer<span class="token punctuation">.</span>MAX_VALUE<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> NANOSECONDS<span class="token punctuation">,</span>          <span class="token comment" spellcheck="true">// 阻塞队列是 DelayedWorkQueue</span>          <span class="token keyword">new</span> <span class="token class-name">DelayedWorkQueue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>常用 API：</p><ul><li><code>ScheduledFuture&lt;?&gt; schedule(Runnable/Callable&lt;V&gt;, long delay, TimeUnit u)</code>：延迟执行任务</li><li><code>ScheduledFuture&lt;?&gt; scheduleAtFixedRate(Runnable/Callable&lt;V&gt;, long initialDelay, long period, TimeUnit unit)</code>：定时执行周期任务，不考虑执行的耗时，参数为初始延迟时间、间隔时间、单位</li><li><code>ScheduledFuture&lt;?&gt; scheduleWithFixedDelay(Runnable/Callable&lt;V&gt;, long initialDelay, long delay, TimeUnit unit)</code>：定时执行周期任务，考虑执行的耗时，参数为初始延迟时间、间隔时间、单位</li></ul><p>基本使用：</p><ul><li><p>延迟任务，但是出现异常并不会在控制台打印，也不会影响其他线程的执行</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span><span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 线程池大小为1时也是串行执行</span>    ScheduledExecutorService executor <span class="token operator">=</span> Executors<span class="token punctuation">.</span><span class="token function">newScheduledThreadPool</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 添加两个任务，都在 1s 后同时执行</span>    executor<span class="token punctuation">.</span><span class="token function">schedule</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"任务1，执行时间："</span> <span class="token operator">+</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">//int i = 1 / 0;</span>        <span class="token keyword">try</span> <span class="token punctuation">{</span> Thread<span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">2000</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">InterruptedException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span>    <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token number">1000</span><span class="token punctuation">,</span> TimeUnit<span class="token punctuation">.</span>MILLISECONDS<span class="token punctuation">)</span><span class="token punctuation">;</span>        executor<span class="token punctuation">.</span><span class="token function">schedule</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"任务2，执行时间："</span> <span class="token operator">+</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token number">1000</span><span class="token punctuation">,</span> TimeUnit<span class="token punctuation">.</span>MILLISECONDS<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>定时任务 <code>scheduleAtFixedRate</code>：<strong>一次任务的启动到下一次任务的启动</strong>之间只要大于等于间隔时间，抢占到 CPU 就会立即执行</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>    ScheduledExecutorService pool <span class="token operator">=</span> Executors<span class="token punctuation">.</span><span class="token function">newScheduledThreadPool</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"start..."</span> <span class="token operator">+</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        pool<span class="token punctuation">.</span><span class="token function">scheduleAtFixedRate</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"running..."</span> <span class="token operator">+</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        Thread<span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">2000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">,</span> TimeUnit<span class="token punctuation">.</span>SECONDS<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token operator">/</span><span class="token operator">*</span>start<span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>Sat Apr <span class="token number">24</span> <span class="token number">18</span><span class="token operator">:</span><span class="token number">08</span><span class="token operator">:</span><span class="token number">12</span> CST <span class="token number">2021</span>running<span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>Sat Apr <span class="token number">24</span> <span class="token number">18</span><span class="token operator">:</span><span class="token number">08</span><span class="token operator">:</span><span class="token number">13</span> CST <span class="token number">2021</span>running<span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>Sat Apr <span class="token number">24</span> <span class="token number">18</span><span class="token operator">:</span><span class="token number">08</span><span class="token operator">:</span><span class="token number">15</span> CST <span class="token number">2021</span>running<span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>Sat Apr <span class="token number">24</span> <span class="token number">18</span><span class="token operator">:</span><span class="token number">08</span><span class="token operator">:</span><span class="token number">17</span> CST <span class="token number">2021</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>定时任务 <code>scheduleWithFixedDelay</code>：<strong>一次任务的结束到下一次任务的启动之间</strong>等于间隔时间，抢占到 CPU 就会立即执行，这个方法才是真正的设置两个任务之间的间隔</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span><span class="token punctuation">{</span>    ScheduledExecutorService pool <span class="token operator">=</span> Executors<span class="token punctuation">.</span><span class="token function">newScheduledThreadPool</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"start..."</span> <span class="token operator">+</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        pool<span class="token punctuation">.</span><span class="token function">scheduleWithFixedDelay</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"running..."</span> <span class="token operator">+</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        Thread<span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">2000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">,</span> TimeUnit<span class="token punctuation">.</span>SECONDS<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token operator">/</span><span class="token operator">*</span>start<span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>Sat Apr <span class="token number">24</span> <span class="token number">18</span><span class="token operator">:</span><span class="token number">11</span><span class="token operator">:</span><span class="token number">41</span> CST <span class="token number">2021</span>running<span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>Sat Apr <span class="token number">24</span> <span class="token number">18</span><span class="token operator">:</span><span class="token number">11</span><span class="token operator">:</span><span class="token number">42</span> CST <span class="token number">2021</span>running<span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>Sat Apr <span class="token number">24</span> <span class="token number">18</span><span class="token operator">:</span><span class="token number">11</span><span class="token operator">:</span><span class="token number">45</span> CST <span class="token number">2021</span>running<span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>Sat Apr <span class="token number">24</span> <span class="token number">18</span><span class="token operator">:</span><span class="token number">11</span><span class="token operator">:</span><span class="token number">48</span> CST <span class="token number">2021</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li></ul><h2 id="❻ForkJoin"><a href="#❻ForkJoin" class="headerlink" title="❻ForkJoin"></a>❻ForkJoin</h2><p>Fork&#x2F;Join：线程池的实现，体现是分治思想，适用于能够进行任务拆分的 CPU 密集型运算，用于<strong>并行计算</strong></p><p>任务拆分：将一个大任务拆分为算法上相同的小任务，直至不能拆分可以直接求解。跟递归相关的一些计算，如归并排序、斐波那契数列都可以用分治思想进行求解</p><ul><li><p>Fork&#x2F;Join 在<strong>分治的基础上加入了多线程</strong>，把每个任务的分解和合并交给不同的线程来完成，提升了运算效率</p></li><li><p>ForkJoin 使用 ForkJoinPool 来启动，是一个特殊的线程池，默认会创建与 CPU 核心数大小相同的线程池</p></li><li><p>任务有返回值继承 RecursiveTask，没有返回值继承 RecursiveAction</p></li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>    ForkJoinPool pool <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ForkJoinPool</span><span class="token punctuation">(</span><span class="token number">4</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>pool<span class="token punctuation">.</span><span class="token function">invoke</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">MyTask</span><span class="token punctuation">(</span><span class="token number">5</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">//拆分  5 + MyTask(4) --> 4 + MyTask(3) --></span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">// 1~ n 之间整数的和</span><span class="token keyword">class</span> <span class="token class-name">MyTask</span> <span class="token keyword">extends</span> <span class="token class-name">RecursiveTask</span><span class="token operator">&lt;</span>Integer<span class="token operator">></span> <span class="token punctuation">{</span>    <span class="token keyword">private</span> <span class="token keyword">int</span> n<span class="token punctuation">;</span>    <span class="token keyword">public</span> <span class="token function">MyTask</span><span class="token punctuation">(</span><span class="token keyword">int</span> n<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">this</span><span class="token punctuation">.</span>n <span class="token operator">=</span> n<span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> String <span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">return</span> <span class="token string">"MyTask{"</span> <span class="token operator">+</span> <span class="token string">"n="</span> <span class="token operator">+</span> n <span class="token operator">+</span> <span class="token string">'}'</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">protected</span> Integer <span class="token function">compute</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 如果 n 已经为 1，可以求得结果了</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>n <span class="token operator">==</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token keyword">return</span> n<span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 将任务进行拆分(fork)</span>        MyTask t1 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">MyTask</span><span class="token punctuation">(</span>n <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        t1<span class="token punctuation">.</span><span class="token function">fork</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 合并(join)结果</span>        <span class="token keyword">int</span> result <span class="token operator">=</span> n <span class="token operator">+</span> t1<span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> result<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>继续拆分优化：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">AddTask</span> <span class="token keyword">extends</span> <span class="token class-name">RecursiveTask</span><span class="token operator">&lt;</span>Integer<span class="token operator">></span> <span class="token punctuation">{</span>    <span class="token keyword">int</span> begin<span class="token punctuation">;</span>    <span class="token keyword">int</span> end<span class="token punctuation">;</span>    <span class="token keyword">public</span> <span class="token function">AddTask</span><span class="token punctuation">(</span><span class="token keyword">int</span> begin<span class="token punctuation">,</span> <span class="token keyword">int</span> end<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">this</span><span class="token punctuation">.</span>begin <span class="token operator">=</span> begin<span class="token punctuation">;</span>        <span class="token keyword">this</span><span class="token punctuation">.</span>end <span class="token operator">=</span> end<span class="token punctuation">;</span>    <span class="token punctuation">}</span>        <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> String <span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">return</span> <span class="token string">"{"</span> <span class="token operator">+</span> begin <span class="token operator">+</span> <span class="token string">","</span> <span class="token operator">+</span> end <span class="token operator">+</span> <span class="token string">'}'</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>        <span class="token annotation punctuation">@Override</span>    <span class="token keyword">protected</span> Integer <span class="token function">compute</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 5, 5</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>begin <span class="token operator">==</span> end<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token keyword">return</span> begin<span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 4, 5  防止多余的拆分  提高效率</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>end <span class="token operator">-</span> begin <span class="token operator">==</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token keyword">return</span> end <span class="token operator">+</span> begin<span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 1 5</span>        <span class="token keyword">int</span> mid <span class="token operator">=</span> <span class="token punctuation">(</span>end <span class="token operator">+</span> begin<span class="token punctuation">)</span> <span class="token operator">/</span> <span class="token number">2</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 3</span>        AddTask t1 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">AddTask</span><span class="token punctuation">(</span>begin<span class="token punctuation">,</span> mid<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 1,3</span>        t1<span class="token punctuation">.</span><span class="token function">fork</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        AddTask t2 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">AddTask</span><span class="token punctuation">(</span>mid <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">,</span> end<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 4,5</span>        t2<span class="token punctuation">.</span><span class="token function">fork</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">int</span> result <span class="token operator">=</span> t1<span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> t2<span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> result<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>ForkJoinPool 实现了<strong>工作窃取算法</strong>来提高 CPU 的利用率：</p><ul><li>每个线程都维护了一个<strong>双端队列</strong>，用来存储需要执行的任务</li><li>工作窃取算法允许空闲的线程从其它线程的双端队列中窃取一个任务来执行</li><li>窃取的必须是<strong>最晚的任务</strong>，避免和队列所属线程发生竞争，但是队列中只有一个任务时还是会发生竞争</li></ul><h1 id="⑥JUC锁"><a href="#⑥JUC锁" class="headerlink" title="⑥JUC锁"></a>⑥JUC锁</h1><h2 id="❶AQS"><a href="#❶AQS" class="headerlink" title="❶AQS"></a>❶AQS</h2><h3 id="核心思想"><a href="#核心思想" class="headerlink" title="核心思想"></a>核心思想</h3><p>AQS：AbstractQueuedSynchronizer，是阻塞式锁和相关的同步器工具的框架，许多同步类实现都依赖于该同步器</p><p>AQS 用状态属性来表示资源的状态（分<strong>独占模式和共享模式</strong>），子类需要定义如何维护这个状态，控制如何获取锁和释放锁</p><ul><li>独占模式是只有一个线程能够访问资源，如 ReentrantLock</li><li>共享模式允许多个线程访问资源，如 Semaphore，ReentrantReadWriteLock 是组合式</li></ul><p>AQS 核心思想：</p><ul><li>如果被请求的共享资源空闲，则将当前请求资源的线程设置为有效的工作线程，并将共享资源设置锁定状态</li><li>如果请求的共享资源被占用，AQS 用CLH 队列实现线程阻塞等待以及被唤醒时锁分配的机制，将暂时获取不到锁的线程加入到队列中</li></ul><p><strong>CLH 队列</strong>是一个<strong>虚拟的双向队列</strong>（即不存在队列实例，仅存在结点之间的关联关系）。</p><p><img src="https://img.jwt1399.top/img/202301041734514.png"></p><p>AQS 是将每条请求共享资源的线程封装成一个 CLH 锁队列的一个结点（Node）来实现锁的分配。在 CLH 同步队列中，一个节点表示一个线程，它保存着线程的引用（thread）、 当前节点在队列中的状态（waitStatus）、前驱节点（prev）、后继节点（next）。同时 AQS 还维护两个指针 Head 和 Tail，分别指向队列的头部和尾部。</p><p><img src="https://img.jwt1399.top/img/202301041734275.png" alt="AQS原理图"></p><h3 id="设计原理"><a href="#设计原理" class="headerlink" title="设计原理"></a>设计原理</h3><p>设计原理：</p><ul><li><p>获取锁：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">while</span><span class="token punctuation">(</span>state 状态不允许获取<span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token comment" spellcheck="true">// tryAcquire(arg)</span>    <span class="token keyword">if</span><span class="token punctuation">(</span>队列中还没有此线程<span class="token punctuation">)</span> <span class="token punctuation">{</span>        入队并阻塞 park    <span class="token punctuation">}</span><span class="token punctuation">}</span>当前线程出队<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>释放锁：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">if</span><span class="token punctuation">(</span>state 状态允许了<span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token comment" spellcheck="true">// tryRelease(arg)</span>    恢复阻塞的线程<span class="token punctuation">(</span>s<span class="token punctuation">)</span> unpark<span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre></li></ul><p>AbstractQueuedSynchronizer 中 state 设计：</p><ul><li><p>state 使用了 32bit int 来维护同步状态，独占模式 0 表示未加锁状态，大于 0 表示已经加锁状态</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">volatile</span> <span class="token keyword">int</span> state<span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre></li><li><p>state <strong>使用 volatile 修饰配合 cas</strong> 保证其修改时的原子性</p></li><li><p>state 表示<strong>线程重入的次数（独占模式）或者剩余许可数（共享模式）</strong></p></li><li><p>state API：</p><ul><li><code>protected final int getState()</code>：获取 state 状态</li><li><code>protected final void setState(int newState)</code>：设置 state 状态</li><li><code>protected final boolean compareAndSetState(int expect,int update)</code>：<strong>CAS</strong> 安全设置 state</li></ul></li></ul><p>封装线程的 Node 节点中 waitstate 设计：</p><ul><li><p>使用 <strong>volatile 修饰配合 CAS</strong> 保证其修改时的原子性</p></li><li><p>表示 Node 节点的状态，有以下几种状态：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// 默认为 0</span><span class="token keyword">volatile</span> <span class="token keyword">int</span> waitStatus<span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 由于超时或中断，此节点被取消，不会再改变状态</span><span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> CANCELLED <span class="token operator">=</span>  <span class="token number">1</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 此节点后面的节点已（或即将）被阻止（通过park），【当前节点在释放或取消时必须唤醒后面的节点】</span><span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> SIGNAL    <span class="token operator">=</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 此节点当前在条件队列中</span><span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> CONDITION <span class="token operator">=</span> <span class="token operator">-</span><span class="token number">2</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 将releaseShared传播到其他节点</span><span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> PROPAGATE <span class="token operator">=</span> <span class="token operator">-</span><span class="token number">3</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li></ul><p>阻塞恢复设计：</p><ul><li>使用 park &amp; unpark 来实现线程的暂停和恢复，因为命令的先后顺序不影响结果</li><li>park &amp; unpark 是针对线程的，而不是针对同步器的，因此控制粒度更为精细</li><li>park 线程可以通过 interrupt 打断</li></ul><p>队列设计：</p><ul><li><p>使用了 FIFO 先入先出队列，并不支持优先级队列，<strong>同步队列是双向链表，便于出队入队</strong></p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// 头结点，指向哑元节点</span><span class="token keyword">private</span> <span class="token keyword">transient</span> <span class="token keyword">volatile</span> Node head<span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 阻塞队列的尾节点，阻塞队列不包含头结点，从 head.next → tail 认为是阻塞队列</span><span class="token keyword">private</span> <span class="token keyword">transient</span> <span class="token keyword">volatile</span> Node tail<span class="token punctuation">;</span><span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">class</span> <span class="token class-name">Node</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 枚举：共享模式</span>    <span class="token keyword">static</span> <span class="token keyword">final</span> Node SHARED <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Node</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 枚举：独占模式</span>    <span class="token keyword">static</span> <span class="token keyword">final</span> Node EXCLUSIVE <span class="token operator">=</span> null<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// node 需要构建成 FIFO 队列，prev 指向前继节点</span>    <span class="token keyword">volatile</span> Node prev<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// next 指向后继节点</span>    <span class="token keyword">volatile</span> Node next<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 当前 node 封装的线程</span>    <span class="token keyword">volatile</span> Thread thread<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 条件队列是单向链表，只有后继指针，条件队列使用该属性</span>    Node nextWaiter<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><img src="https://img.jwt1399.top/img/202301041747575.png"></p></li><li><p>条件变量来实现等待、唤醒机制，支持多个条件变量，类似于 Monitor 的 WaitSet，<strong>条件队列是单向链表</strong></p><pre class="line-numbers language-java"><code class="language-java"> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ConditionObject</span> <span class="token keyword">implements</span> <span class="token class-name">Condition</span><span class="token punctuation">,</span> java<span class="token punctuation">.</span>io<span class="token punctuation">.</span>Serializable <span class="token punctuation">{</span>     <span class="token comment" spellcheck="true">// 指向条件队列的第一个 node 节点</span>     <span class="token keyword">private</span> <span class="token keyword">transient</span> Node firstWaiter<span class="token punctuation">;</span>     <span class="token comment" spellcheck="true">// 指向条件队列的最后一个 node 节点</span>     <span class="token keyword">private</span> <span class="token keyword">transient</span> Node lastWaiter<span class="token punctuation">;</span> <span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li></ul><h3 id="模板对象"><a href="#模板对象" class="headerlink" title="模板对象"></a>模板对象</h3><p>同步器的设计是基于模板方法模式，该模式是基于继承的，主要是为了在不改变模板结构的前提下在子类中重新定义模板中的内容以实现复用代码</p><ul><li>使用者继承 <code>AbstractQueuedSynchronizer</code> 并重写指定的方法</li><li>将 AQS 组合在自定义同步组件的实现中，并调用其模板方法，这些模板方法会调用使用者重写的方法</li></ul><p>AQS 使用了模板方法模式，自定义同步器时需要重写下面几个 AQS 提供的模板方法：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token function">isHeldExclusively</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true">//该线程是否正在独占资源。只有用到condition才需要去实现它</span><span class="token function">tryAcquire</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true">//独占方式。尝试获取资源，成功则返回true，失败则返回false</span><span class="token function">tryRelease</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true">//独占方式。尝试释放资源，成功则返回true，失败则返回false</span><span class="token function">tryAcquireShared</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true">//共享方式。尝试获取资源。负数表示失败；0表示成功但没有剩余可用资源；正数表示成功且有剩余资源</span><span class="token function">tryReleaseShared</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true">//共享方式。尝试释放资源，成功则返回true，失败则返回false</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>默认情况下，每个方法都抛出 <code>UnsupportedOperationException</code></li><li>这些方法的实现必须是内部线程安全的</li><li>AQS 类中的其他方法都是 final ，所以无法被其他类使用，只有这几个方法可以被其他类使用</li></ul><h3 id="自定义不可重入锁"><a href="#自定义不可重入锁" class="headerlink" title="自定义不可重入锁"></a>自定义不可重入锁</h3><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">MyLock</span> <span class="token keyword">implements</span> <span class="token class-name">Lock</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">//独占锁 不可重入</span>    <span class="token keyword">class</span> <span class="token class-name">MySync</span> <span class="token keyword">extends</span> <span class="token class-name">AbstractQueuedSynchronizer</span> <span class="token punctuation">{</span>        <span class="token annotation punctuation">@Override</span>        <span class="token keyword">protected</span> <span class="token keyword">boolean</span> <span class="token function">tryAcquire</span><span class="token punctuation">(</span><span class="token keyword">int</span> arg<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">compareAndSetState</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token comment" spellcheck="true">// 加上锁 设置 owner 为当前线程</span>                <span class="token function">setExclusiveOwnerThread</span><span class="token punctuation">(</span>Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>            <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token annotation punctuation">@Override</span>   <span class="token comment" spellcheck="true">//解锁</span>        <span class="token keyword">protected</span> <span class="token keyword">boolean</span> <span class="token function">tryRelease</span><span class="token punctuation">(</span><span class="token keyword">int</span> arg<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token function">setExclusiveOwnerThread</span><span class="token punctuation">(</span>null<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token function">setState</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//volatile 修饰的变量放在后面，防止指令重排</span>            <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token annotation punctuation">@Override</span>   <span class="token comment" spellcheck="true">//是否持有独占锁</span>        <span class="token keyword">protected</span> <span class="token keyword">boolean</span> <span class="token function">isHeldExclusively</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token keyword">return</span> <span class="token function">getState</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token number">1</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">public</span> Condition <span class="token function">newCondition</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">ConditionObject</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span>    <span class="token keyword">private</span> MySync sync <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">MySync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token annotation punctuation">@Override</span>   <span class="token comment" spellcheck="true">//加锁（不成功进入等待队列等待）</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        sync<span class="token punctuation">.</span><span class="token function">acquire</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@Override</span>   <span class="token comment" spellcheck="true">//加锁 可打断</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">lockInterruptibly</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">throws</span> InterruptedException <span class="token punctuation">{</span>        sync<span class="token punctuation">.</span><span class="token function">acquireInterruptibly</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@Override</span>   <span class="token comment" spellcheck="true">//尝试加锁，尝试一次</span>    <span class="token keyword">public</span> <span class="token keyword">boolean</span> <span class="token function">tryLock</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">return</span> sync<span class="token punctuation">.</span><span class="token function">tryAcquire</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@Override</span>   <span class="token comment" spellcheck="true">//尝试加锁，带超时</span>    <span class="token keyword">public</span> <span class="token keyword">boolean</span> <span class="token function">tryLock</span><span class="token punctuation">(</span><span class="token keyword">long</span> time<span class="token punctuation">,</span> TimeUnit unit<span class="token punctuation">)</span> <span class="token keyword">throws</span> InterruptedException <span class="token punctuation">{</span>        <span class="token keyword">return</span> sync<span class="token punctuation">.</span><span class="token function">tryAcquireNanos</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> unit<span class="token punctuation">.</span><span class="token function">toNanos</span><span class="token punctuation">(</span>time<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>        <span class="token annotation punctuation">@Override</span>   <span class="token comment" spellcheck="true">//解锁</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        sync<span class="token punctuation">.</span><span class="token function">release</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>        <span class="token annotation punctuation">@Override</span>   <span class="token comment" spellcheck="true">//条件变量</span>    <span class="token keyword">public</span> Condition <span class="token function">newCondition</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">return</span> sync<span class="token punctuation">.</span><span class="token function">newCondition</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="❷ReentrantLock"><a href="#❷ReentrantLock" class="headerlink" title="❷ReentrantLock"></a>❷ReentrantLock</h2><h3 id="⓿基本使用"><a href="#⓿基本使用" class="headerlink" title="⓿基本使用"></a>⓿基本使用</h3><p>构造方法：<code>ReentrantLock lock = new ReentrantLock(true)</code></p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token function">ReentrantLock</span><span class="token punctuation">(</span><span class="token keyword">boolean</span> fair<span class="token punctuation">)</span> <span class="token punctuation">{</span>    sync <span class="token operator">=</span> fair <span class="token operator">?</span> <span class="token keyword">new</span> <span class="token class-name">FairSync</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">:</span> <span class="token keyword">new</span> <span class="token class-name">NonfairSync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>ReentrantLock 默认是不公平的：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token function">ReentrantLock</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    sync <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">NonfairSync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>说明：公平锁一般没有必要，会降低并发度</p><h3 id="❶非公平锁原理"><a href="#❶非公平锁原理" class="headerlink" title="❶非公平锁原理"></a>❶非公平锁原理</h3><h4 id="加解锁流程"><a href="#加解锁流程" class="headerlink" title="加解锁流程"></a>加解锁流程</h4><table><thead><tr><th>Thread-0执行，没有竞争时</th><th>Thread-1执行，第一个竞争出现</th></tr></thead><tbody><tr><td><img src="https://img.jwt1399.top/img/202301042023014.png"></td><td><img src="https://img.jwt1399.top/img/202301042023877.png"></td></tr></tbody></table><p>Thread-1 执行时</p><ol><li>CAS 尝试将 state 由 0 改为 1，结果失败（第一次）</li><li>进入 <code>tryAcquire</code> 逻辑，这时 state 已经是 1，结果仍然失败（第二次）</li><li>进入 <code>addWaiter</code> 逻辑，构造 Node 队列<ul><li>图中黄色三角表示该 Node 的 waitStatus 状态，其中 0 为默认正常状态</li><li>Node 的创建是懒惰的</li><li>其中第一个 Node 称为 Dummy（哑元）或哨兵，用来占位，并不关联线程</li></ul></li></ol><p><img src="https://img.jwt1399.top/img/202301042023669.png"></p><p>线程节点加入阻塞队列成功，当前线程进入 <code>acquireQueued</code> 逻辑，阻塞线程</p><ol><li><code>acquireQueued</code> 会在一个死循环（自旋）中不断尝试获得锁，失败后进入 park 阻塞</li><li>如果当前线程是在 head 节点后，再次 <code>tryAcquire</code> 尝试获取锁，当然这时 state 仍为 1，失败（第三次）</li><li>进入 <code>shouldParkAfterFailedAcquire</code> 逻辑，将前驱 node，即 head 的 waitStatus 改为 -1，这次返回 false，waitStatus 为 -1 的节点用来唤醒下一个节点</li></ol><p><img src="https://img.jwt1399.top/img/202301042024786.png"></p><ol start="4"><li><code>shouldParkAfterFailedAcquire</code> 执行完毕回到 <code>acquireQueued</code> ，再次 <code>tryAcquire</code> 尝试获取锁，当然这时 state 仍为 1，失败（第四次）</li><li>当再次进入 <code>shouldParkAfterFailedAcquire</code> 时，这时因为其前驱 node 的 waitStatus 已经是 -1，这次返回 true</li><li>进入 parkAndCheckInterrupt， Thread-1 park（灰色表示）</li></ol><p><img src="https://img.jwt1399.top/img/202301042024607.png"></p><p>再次有多个线程经历上述过程竞争失败，变成这个样子</p><p><img src="https://img.jwt1399.top/img/202301042024307.png"></p><p>Thread-0 释放锁，进入 <code>tryRelease</code> 流程，如果成功</p><ul><li>设置 exclusiveOwnerThread 为 null</li><li>state &#x3D; 0</li></ul><p><img src="https://img.jwt1399.top/img/202301042024640.png"></p><p>当前队列不为 null，并且 head 的 waitStatus &#x3D; -1，进入 <code>unparkSuccessor</code> 流程</p><p>找到队列中离 head 最近的一个 Node（没取消的），unpark 恢复其运行，本例中即为 Thread-1</p><p>回到 Thread-1 的 <code>acquireQueued</code> 流程</p><p><img src="https://img.jwt1399.top/img/202301042024310.png"></p><p>如果加锁成功（没有竞争），会设置</p><ul><li>exclusiveOwnerThread 为 Thread-1，state &#x3D; 1</li><li>head 指向刚刚 Thread-1 所在的 Node，该 Node 清空 Thread</li><li>原本的 head 因为从链表断开，而可被垃圾回收</li></ul><p>如果这时候有其它线程来竞争（非公平的体现），例如这时有 Thread-4 来了</p><p><img src="https://img.jwt1399.top/img/202301042024253.png"></p><p>如果不巧又被 Thread-4 占了先</p><ul><li>Thread-4 被设置为 exclusiveOwnerThread，state &#x3D; 1</li><li>Thread-1 再次进入 acquireQueued 流程，获取锁失败，重新进入 park 阻塞</li></ul><h4 id="加锁源码"><a href="#加锁源码" class="headerlink" title="加锁源码"></a>加锁源码</h4><p>ReentrantLock 默认是不公平的：NonfairSync 继承自 AQS</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token function">ReentrantLock</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    sync <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">NonfairSync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    sync<span class="token punctuation">.</span><span class="token function">lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li><p>没有竞争：ExclusiveOwnerThread 属于 Thread-0，state 设置为 1</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// ReentrantLock.NonfairSync#lock</span><span class="token keyword">final</span> <span class="token keyword">void</span> <span class="token function">lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 用 cas 尝试（仅尝试一次）将 state 从 0 改为 1, 如果成功表示【获得了独占锁】</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">compareAndSetState</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span>        <span class="token comment" spellcheck="true">// 设置当前线程为独占线程</span>        <span class="token function">setExclusiveOwnerThread</span><span class="token punctuation">(</span>Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">else</span>        <span class="token function">acquire</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//失败进入</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>第一个竞争出现：Thread-1 执行，CAS 尝试将 state 由 0 改为 1，结果失败（第一次），进入 acquire 逻辑</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// AbstractQueuedSynchronizer#acquire</span><span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token keyword">void</span> <span class="token function">acquire</span><span class="token punctuation">(</span><span class="token keyword">int</span> arg<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// tryAcquire 尝试获取锁失败时, 会调用 addWaiter 将当前线程封装成node入队，acquireQueued 阻塞当前线程</span>    <span class="token comment" spellcheck="true">// acquireQueued 返回 true 表示挂起过程中线程被中断唤醒过，false 表示未被中断过</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">tryAcquire</span><span class="token punctuation">(</span>arg<span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> <span class="token function">acquireQueued</span><span class="token punctuation">(</span><span class="token function">addWaiter</span><span class="token punctuation">(</span>Node<span class="token punctuation">.</span>EXCLUSIVE<span class="token punctuation">)</span><span class="token punctuation">,</span> arg<span class="token punctuation">)</span><span class="token punctuation">)</span>        <span class="token comment" spellcheck="true">// 如果线程被中断了逻辑来到这，完成一次真正的打断效果</span>        <span class="token function">selfInterrupt</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li></ul><img src="https://img.jwt1399.top/img/202301042023877.png" style="zoom:50%;" /><ul><li><p>进入 tryAcquire 尝试获取锁逻辑，这时 state 已经是1，结果仍然失败（第二次），加锁成功有两种情况：</p><ul><li>当前 AQS 处于无锁状态</li><li>加锁线程就是当前线程，说明发生了锁重入</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// ReentrantLock.NonfairSync#tryAcquire</span><span class="token keyword">protected</span> <span class="token keyword">final</span> <span class="token keyword">boolean</span> <span class="token function">tryAcquire</span><span class="token punctuation">(</span><span class="token keyword">int</span> acquires<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">return</span> <span class="token function">nonfairTryAcquire</span><span class="token punctuation">(</span>acquires<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">// 抢占成功返回 true，抢占失败返回 false</span><span class="token keyword">final</span> <span class="token keyword">boolean</span> <span class="token function">nonfairTryAcquire</span><span class="token punctuation">(</span><span class="token keyword">int</span> acquires<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">final</span> Thread current <span class="token operator">=</span> Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// state 值</span>    <span class="token keyword">int</span> c <span class="token operator">=</span> <span class="token function">getState</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 条件成立说明当前处于【无锁状态】</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>c <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">//如果还没有获得锁，尝试用cas获得，这里体现非公平性: 不去检查 AQS 队列是否有阻塞线程直接获取锁        </span>        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">compareAndSetState</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> acquires<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 获取锁成功设置当前线程为独占锁线程。</span>            <span class="token function">setExclusiveOwnerThread</span><span class="token punctuation">(</span>current<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>         <span class="token punctuation">}</span>        <span class="token punctuation">}</span>           <span class="token comment" spellcheck="true">// 如果已经有线程获得了锁, 独占锁线程还是当前线程, 表示【发生了锁重入】</span>    <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>current <span class="token operator">==</span> <span class="token function">getExclusiveOwnerThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 更新锁重入的值</span>        <span class="token keyword">int</span> nextc <span class="token operator">=</span> c <span class="token operator">+</span> acquires<span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 越界判断，当重入的深度很深时，会导致 nextc &lt; 0，int值达到最大之后再 + 1 变负数</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>nextc <span class="token operator">&lt;</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// overflow</span>            <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token string">"Maximum lock count exceeded"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 更新 state 的值，这里不使用 cas 是因为当前线程正在持有锁，所以这里的操作相当于在一个管程内</span>        <span class="token function">setState</span><span class="token punctuation">(</span>nextc<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">// 获取失败</span>    <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>接下来进入 addWaiter 逻辑，构造 Node 队列，前置条件是当前线程获取锁失败，说明有线程占用了锁</p><ul><li>图中黄色三角表示该 Node 的 waitStatus 状态，其中 0 为默认<strong>正常状态</strong></li><li>Node 的创建是懒惰的，其中第一个 Node 称为 <strong>Dummy（哑元）或哨兵</strong>，用来占位，并不关联线程</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// AbstractQueuedSynchronizer#addWaiter，返回当前线程的 node 节点</span><span class="token keyword">private</span> Node <span class="token function">addWaiter</span><span class="token punctuation">(</span>Node mode<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 将当前线程关联到一个 Node 对象上, 模式为独占模式   </span>    Node node <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Node</span><span class="token punctuation">(</span>Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> mode<span class="token punctuation">)</span><span class="token punctuation">;</span>    Node pred <span class="token operator">=</span> tail<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 快速入队，如果 tail 不为 null，说明存在阻塞队列</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>pred <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 将当前节点的前驱节点指向 尾节点</span>        node<span class="token punctuation">.</span>prev <span class="token operator">=</span> pred<span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 通过 cas 将 Node 对象加入 AQS 队列，成为尾节点，【尾插法】</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">compareAndSetTail</span><span class="token punctuation">(</span>pred<span class="token punctuation">,</span> node<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            pred<span class="token punctuation">.</span>next <span class="token operator">=</span> node<span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 双向链表</span>            <span class="token keyword">return</span> node<span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">// 初始时队列为空，或者 CAS 失败进入这里</span>    <span class="token function">enq</span><span class="token punctuation">(</span>node<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> node<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// AbstractQueuedSynchronizer#enq</span><span class="token keyword">private</span> Node <span class="token function">enq</span><span class="token punctuation">(</span><span class="token keyword">final</span> Node node<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 自旋入队，必须入队成功才结束循环</span>    <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token punctuation">;</span><span class="token punctuation">;</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        Node t <span class="token operator">=</span> tail<span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 说明当前锁被占用，且当前线程可能是【第一个获取锁失败】的线程，【还没有建立队列】</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>t <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 设置一个【哑元节点】，头尾指针都指向该节点</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">compareAndSetHead</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Node</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span>                tail <span class="token operator">=</span> head<span class="token punctuation">;</span>        <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 自旋到这，普通入队方式，首先赋值尾节点的前驱节点【尾插法】</span>            node<span class="token punctuation">.</span>prev <span class="token operator">=</span> t<span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">// 【在设置完尾节点后，才更新的原始尾节点的后继节点，所以此时从前往后遍历会丢失尾节点】</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">compareAndSetTail</span><span class="token punctuation">(</span>t<span class="token punctuation">,</span> node<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token comment" spellcheck="true">//【此时 t.next  = null，并且这里已经 CAS 结束，线程并不是安全的】</span>                t<span class="token punctuation">.</span>next <span class="token operator">=</span> node<span class="token punctuation">;</span>                <span class="token keyword">return</span> t<span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 返回当前 node 的前驱节点</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><img src="https://img.jwt1399.top/img/202301042023669.png"></p></li><li><p>线程节点加入阻塞队列成功，进入 AbstractQueuedSynchronizer#acquireQueued 逻辑阻塞线程</p><ul><li><p>acquireQueued 会在一个自旋中不断尝试获得锁，失败后进入 park 阻塞</p></li><li><p>如果当前线程是在 head 节点后，会再次 tryAcquire 尝试获取锁，state 仍为 1 则失败（第三次）</p></li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">final</span> <span class="token keyword">boolean</span> <span class="token function">acquireQueued</span><span class="token punctuation">(</span><span class="token keyword">final</span> Node node<span class="token punctuation">,</span> <span class="token keyword">int</span> arg<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// true 表示当前线程抢占锁失败，false 表示成功</span>    <span class="token keyword">boolean</span> failed <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>    <span class="token keyword">try</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 中断标记，表示当前线程是否被中断</span>        <span class="token keyword">boolean</span> interrupted <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>        <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token punctuation">;</span><span class="token punctuation">;</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 获得当前线程节点的前驱节点</span>            <span class="token keyword">final</span> Node p <span class="token operator">=</span> node<span class="token punctuation">.</span><span class="token function">predecessor</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">// 前驱节点是 head, FIFO 队列的特性表示轮到当前线程可以去获取锁</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span>p <span class="token operator">==</span> head <span class="token operator">&amp;&amp;</span> <span class="token function">tryAcquire</span><span class="token punctuation">(</span>arg<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token comment" spellcheck="true">// 获取成功, 设置当前线程自己的 node 为 head</span>                <span class="token function">setHead</span><span class="token punctuation">(</span>node<span class="token punctuation">)</span><span class="token punctuation">;</span>                p<span class="token punctuation">.</span>next <span class="token operator">=</span> null<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// help GC</span>                <span class="token comment" spellcheck="true">// 表示抢占锁成功</span>                failed <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>                <span class="token comment" spellcheck="true">// 返回当前线程是否被中断</span>                <span class="token keyword">return</span> interrupted<span class="token punctuation">;</span>            <span class="token punctuation">}</span>            <span class="token comment" spellcheck="true">// 判断是否应当 park，返回 false 后需要新一轮的循环，返回 true 进入条件二阻塞线程</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">shouldParkAfterFailedAcquire</span><span class="token punctuation">(</span>p<span class="token punctuation">,</span> node<span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> <span class="token function">parkAndCheckInterrupt</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>                <span class="token comment" spellcheck="true">// 条件二返回结果是当前线程是否被打断，没有被打断返回 false 不进入这里的逻辑</span>                <span class="token comment" spellcheck="true">// 【就算被打断了，也会继续循环，并不会返回】</span>                interrupted <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 【可打断模式下才会进入该逻辑】</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>failed<span class="token punctuation">)</span>            <span class="token function">cancelAcquire</span><span class="token punctuation">(</span>node<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>进入 shouldParkAfterFailedAcquire 逻辑，<strong>将前驱 node 的 waitStatus 改为 -1</strong>，返回 false；waitStatus 为 -1 的节点用来唤醒下一个节点</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">boolean</span> <span class="token function">shouldParkAfterFailedAcquire</span><span class="token punctuation">(</span>Node pred<span class="token punctuation">,</span> Node node<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">int</span> ws <span class="token operator">=</span> pred<span class="token punctuation">.</span>waitStatus<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 表示前置节点是个可以唤醒当前节点的节点，返回 true</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>ws <span class="token operator">==</span> Node<span class="token punctuation">.</span>SIGNAL<span class="token punctuation">)</span>        <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 前置节点的状态处于取消状态，需要【删除前面所有取消的节点】, 返回到外层循环重试</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>ws <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">do</span> <span class="token punctuation">{</span>            node<span class="token punctuation">.</span>prev <span class="token operator">=</span> pred <span class="token operator">=</span> pred<span class="token punctuation">.</span>prev<span class="token punctuation">;</span>        <span class="token punctuation">}</span> <span class="token keyword">while</span> <span class="token punctuation">(</span>pred<span class="token punctuation">.</span>waitStatus <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 获取到非取消的节点，连接上当前节点</span>        pred<span class="token punctuation">.</span>next <span class="token operator">=</span> node<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 默认情况下 node 的 waitStatus 是 0，进入这里的逻辑</span>    <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 【设置上一个节点状态为 Node.SIGNAL】，返回外层循环重试</span>        <span class="token function">compareAndSetWaitStatus</span><span class="token punctuation">(</span>pred<span class="token punctuation">,</span> ws<span class="token punctuation">,</span> Node<span class="token punctuation">.</span>SIGNAL<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">// 返回不应该 park，再次尝试一次</span>    <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>shouldParkAfterFailedAcquire 执行完毕回到 acquireQueued ，再次 tryAcquire 尝试获取锁，这时 state 仍为 1 获取失败（第四次）</li><li>当再次进入 shouldParkAfterFailedAcquire 时，这时其前驱 node 的 waitStatus 已经是 -1 了，返回 true</li><li>进入 parkAndCheckInterrupt， Thread-1 park（灰色表示）</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">final</span> <span class="token keyword">boolean</span> <span class="token function">parkAndCheckInterrupt</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 阻塞当前线程，如果打断标记已经是 true, 则 park 会失效</span>    LockSupport<span class="token punctuation">.</span><span class="token function">park</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 判断当前线程是否被打断，清除打断标记</span>    <span class="token keyword">return</span> Thread<span class="token punctuation">.</span><span class="token function">interrupted</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>再有多个线程经历竞争失败后：</p><p><img src="https://img.jwt1399.top/img/202301042024307.png"></p></li></ul><h4 id="解锁源码"><a href="#解锁源码" class="headerlink" title="解锁源码"></a>解锁源码</h4><p>ReentrantLock#unlock：释放锁</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    sync<span class="token punctuation">.</span><span class="token function">release</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>Thread-0 释放锁，进入 release 流程</p><ul><li><p>进入 tryRelease，设置 exclusiveOwnerThread 为 null，state &#x3D; 0</p></li><li><p>当前队列不为 null，并且 head 的 waitStatus &#x3D; -1，进入 unparkSuccessor</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// AbstractQueuedSynchronizer#release</span><span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token keyword">boolean</span> <span class="token function">release</span><span class="token punctuation">(</span><span class="token keyword">int</span> arg<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 尝试释放锁，tryRelease 返回 true 表示当前线程已经【完全释放锁，重入的释放了】</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">tryRelease</span><span class="token punctuation">(</span>arg<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 队列头节点</span>        Node h <span class="token operator">=</span> head<span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 头节点什么时候是空？没有发生锁竞争，没有竞争线程创建哑元节点</span>        <span class="token comment" spellcheck="true">// 条件成立说明阻塞队列有等待线程，需要唤醒 head 节点后面的线程</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>h <span class="token operator">!=</span> null <span class="token operator">&amp;&amp;</span> h<span class="token punctuation">.</span>waitStatus <span class="token operator">!=</span> <span class="token number">0</span><span class="token punctuation">)</span>            <span class="token function">unparkSuccessor</span><span class="token punctuation">(</span>h<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>        <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// ReentrantLock.Sync#tryRelease</span><span class="token keyword">protected</span> <span class="token keyword">final</span> <span class="token keyword">boolean</span> <span class="token function">tryRelease</span><span class="token punctuation">(</span><span class="token keyword">int</span> releases<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 减去释放的值，可能重入</span>    <span class="token keyword">int</span> c <span class="token operator">=</span> <span class="token function">getState</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span> releases<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 如果当前线程不是持有锁的线程直接报错</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">!=</span> <span class="token function">getExclusiveOwnerThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>        <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">IllegalMonitorStateException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 是否已经完全释放锁</span>    <span class="token keyword">boolean</span> free <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 支持锁重入, 只有 state 减为 0, 才完全释放锁成功</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>c <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        free <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>        <span class="token function">setExclusiveOwnerThread</span><span class="token punctuation">(</span>null<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">// 当前线程就是持有锁线程，所以可以直接更新锁，不需要使用 CAS</span>    <span class="token function">setState</span><span class="token punctuation">(</span>c<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> free<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>进入 AbstractQueuedSynchronizer#unparkSuccessor 方法，唤醒当前节点的后继节点</p><ul><li>找到队列中距离 head 最近的一个没取消的 Node，unpark 恢复其运行，本例中即为 Thread-1</li><li>回到 Thread-1 的 acquireQueued 流程</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">unparkSuccessor</span><span class="token punctuation">(</span>Node node<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 当前节点的状态</span>    <span class="token keyword">int</span> ws <span class="token operator">=</span> node<span class="token punctuation">.</span>waitStatus<span class="token punctuation">;</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>ws <span class="token operator">&lt;</span> <span class="token number">0</span><span class="token punctuation">)</span>                <span class="token comment" spellcheck="true">// 【尝试重置状态为 0】，因为当前节点要完成对后续节点的唤醒任务了，不需要 -1 了</span>        <span class="token function">compareAndSetWaitStatus</span><span class="token punctuation">(</span>node<span class="token punctuation">,</span> ws<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 找到需要 unpark 的节点，当前节点的下一个    </span>    Node s <span class="token operator">=</span> node<span class="token punctuation">.</span>next<span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 已取消的节点不能唤醒，需要找到距离头节点最近的非取消的节点</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>s <span class="token operator">==</span> null <span class="token operator">||</span> s<span class="token punctuation">.</span>waitStatus <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        s <span class="token operator">=</span> null<span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// AQS 队列【从后至前】找需要 unpark 的节点，直到 t == 当前的 node 为止，找不到就不唤醒了</span>        <span class="token keyword">for</span> <span class="token punctuation">(</span>Node t <span class="token operator">=</span> tail<span class="token punctuation">;</span> t <span class="token operator">!=</span> null <span class="token operator">&amp;&amp;</span> t <span class="token operator">!=</span> node<span class="token punctuation">;</span> t <span class="token operator">=</span> t<span class="token punctuation">.</span>prev<span class="token punctuation">)</span>            <span class="token comment" spellcheck="true">// 说明当前线程状态需要被唤醒</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span>t<span class="token punctuation">.</span>waitStatus <span class="token operator">&lt;=</span> <span class="token number">0</span><span class="token punctuation">)</span>                <span class="token comment" spellcheck="true">// 置换引用</span>                s <span class="token operator">=</span> t<span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">// 【找到合适的可以被唤醒的 node，则唤醒线程】</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>s <span class="token operator">!=</span> null<span class="token punctuation">)</span>        LockSupport<span class="token punctuation">.</span><span class="token function">unpark</span><span class="token punctuation">(</span>s<span class="token punctuation">.</span>thread<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>从后向前的唤醒的原因</strong>：enq 方法中，节点是尾插法，首先赋值的是尾节点的前驱节点，此时前驱节点的 next 并没有指向尾节点，从前遍历会丢失尾节点</p></li><li><p>唤醒的线程会从 park 位置开始执行，如果加锁成功（没有竞争），会设置</p><ul><li>exclusiveOwnerThread 为 Thread-1，state &#x3D; 1</li><li>head 指向刚刚 Thread-1 所在的 Node，该 Node 会清空 Thread</li><li>原本的 head 因为从链表断开，而可被垃圾回收（图中有错误，原来的头节点的 waitStatus 被改为 0 了）</li></ul><p><img src="https://img.jwt1399.top/img/202301042024310.png"></p></li><li><p>如果这时有其它线程来竞争<strong>（非公平）</strong>，例如这时有 Thread-4 来了并抢占了锁</p><ul><li>Thread-4 被设置为 exclusiveOwnerThread，state &#x3D; 1</li><li>Thread-1 再次进入 acquireQueued 流程，获取锁失败，重新进入 park 阻塞</li></ul></li></ul><p><img src="https://img.jwt1399.top/img/202301042024253.png"></p><h3 id="❷公平锁原理"><a href="#❷公平锁原理" class="headerlink" title="❷公平锁原理"></a>❷公平锁原理</h3><p>与非公平锁主要区别在于 tryAcquire 方法：先检查 AQS 队列中是否有前驱节点，没有才去 CAS 竞争</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">class</span> <span class="token class-name">FairSync</span> <span class="token keyword">extends</span> <span class="token class-name">Sync</span> <span class="token punctuation">{</span>    <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">long</span> serialVersionUID <span class="token operator">=</span> <span class="token operator">-</span>3000897897090466540L<span class="token punctuation">;</span>    <span class="token keyword">final</span> <span class="token keyword">void</span> <span class="token function">lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token function">acquire</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">protected</span> <span class="token keyword">final</span> <span class="token keyword">boolean</span> <span class="token function">tryAcquire</span><span class="token punctuation">(</span><span class="token keyword">int</span> acquires<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">final</span> Thread current <span class="token operator">=</span> Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">int</span> c <span class="token operator">=</span> <span class="token function">getState</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>c <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 先检查 AQS 队列中是否有前驱节点, 没有(false)才去竞争</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">hasQueuedPredecessors</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span>                <span class="token function">compareAndSetState</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> acquires<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token function">setExclusiveOwnerThread</span><span class="token punctuation">(</span>current<span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 锁重入</span>        <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token keyword">boolean</span> <span class="token function">hasQueuedPredecessors</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        Node t <span class="token operator">=</span> tail<span class="token punctuation">;</span>    Node h <span class="token operator">=</span> head<span class="token punctuation">;</span>    Node s<span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 头尾指向一个节点，链表为空，返回false</span>    <span class="token keyword">return</span> h <span class="token operator">!=</span> t <span class="token operator">&amp;&amp;</span>        <span class="token comment" spellcheck="true">// 头尾之间有节点，判断头节点的下一个是不是空</span>        <span class="token comment" spellcheck="true">// 不是空进入最后的判断，第二个节点的线程是否是本线程，不是返回 true，表示当前节点有前驱节点</span>        <span class="token punctuation">(</span><span class="token punctuation">(</span>s <span class="token operator">=</span> h<span class="token punctuation">.</span>next<span class="token punctuation">)</span> <span class="token operator">==</span> null <span class="token operator">||</span> s<span class="token punctuation">.</span>thread <span class="token operator">!=</span> Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="❸可重入原理"><a href="#❸可重入原理" class="headerlink" title="❸可重入原理"></a>❸可重入原理</h3><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">class</span> <span class="token class-name">NonfairSync</span> <span class="token keyword">extends</span> <span class="token class-name">Sync</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// ...</span>        <span class="token comment" spellcheck="true">// Sync 继承过来的方法, 方便阅读, 放在此处</span>    <span class="token keyword">final</span> <span class="token keyword">boolean</span> <span class="token function">nonfairTryAcquire</span><span class="token punctuation">(</span><span class="token keyword">int</span> acquires<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">final</span> Thread current <span class="token operator">=</span> Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">int</span> c <span class="token operator">=</span> <span class="token function">getState</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>c <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">compareAndSetState</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> acquires<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token function">setExclusiveOwnerThread</span><span class="token punctuation">(</span>current<span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 如果已经获得了锁, 线程还是当前线程, 表示发生了锁重入</span>        <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>current <span class="token operator">==</span> <span class="token function">getExclusiveOwnerThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// state++</span>            <span class="token keyword">int</span> nextc <span class="token operator">=</span> c <span class="token operator">+</span> acquires<span class="token punctuation">;</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span>nextc <span class="token operator">&lt;</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// overflow</span>                <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token string">"Maximum lock count exceeded"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token function">setState</span><span class="token punctuation">(</span>nextc<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">// Sync 继承过来的方法, 方便阅读, 放在此处</span>    <span class="token keyword">protected</span> <span class="token keyword">final</span> <span class="token keyword">boolean</span> <span class="token function">tryRelease</span><span class="token punctuation">(</span><span class="token keyword">int</span> releases<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// state--</span>        <span class="token keyword">int</span> c <span class="token operator">=</span> <span class="token function">getState</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span> releases<span class="token punctuation">;</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">!=</span> <span class="token function">getExclusiveOwnerThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>            <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">IllegalMonitorStateException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">boolean</span> free <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 支持锁重入, 只有 state 减为 0, 才释放成功</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>c <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            free <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>            <span class="token function">setExclusiveOwnerThread</span><span class="token punctuation">(</span>null<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token function">setState</span><span class="token punctuation">(</span>c<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> free<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="❹可打断原理"><a href="#❹可打断原理" class="headerlink" title="❹可打断原理"></a>❹可打断原理</h3><p>1.不可打断模式：即使它被打断，仍会驻留在 AQS 阻塞队列中，一直要<strong>等到获得锁后才能得知自己被打断</strong>了</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token keyword">void</span> <span class="token function">acquire</span><span class="token punctuation">(</span><span class="token keyword">int</span> arg<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">tryAcquire</span><span class="token punctuation">(</span>arg<span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> <span class="token function">acquireQueued</span><span class="token punctuation">(</span><span class="token function">addWaiter</span><span class="token punctuation">(</span>Node<span class="token punctuation">.</span>EXCLUSIVE<span class="token punctuation">)</span><span class="token punctuation">,</span> arg<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true">//阻塞等待        </span>        <span class="token comment" spellcheck="true">// 如果acquireQueued返回true，打断状态 interrupted = true        </span>        <span class="token function">selfInterrupt</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">selfInterrupt</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 知道自己被打断了，需要重新产生一次中断完成中断效果</span>    Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">interrupt</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">final</span> <span class="token keyword">boolean</span> <span class="token function">acquireQueued</span><span class="token punctuation">(</span><span class="token keyword">final</span> Node node<span class="token punctuation">,</span> <span class="token keyword">int</span> arg<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">try</span> <span class="token punctuation">{</span>                <span class="token keyword">boolean</span> interrupted <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>                <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token punctuation">;</span><span class="token punctuation">;</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                        <span class="token keyword">final</span> Node p <span class="token operator">=</span> node<span class="token punctuation">.</span><span class="token function">predecessor</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                        <span class="token keyword">if</span> <span class="token punctuation">(</span>p <span class="token operator">==</span> head <span class="token operator">&amp;&amp;</span> <span class="token function">tryAcquire</span><span class="token punctuation">(</span>arg<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                                <span class="token function">setHead</span><span class="token punctuation">(</span>node<span class="token punctuation">)</span><span class="token punctuation">;</span>                                p<span class="token punctuation">.</span>next <span class="token operator">=</span> null<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// help GC                </span>                failed <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>                                <span class="token comment" spellcheck="true">// 还是需要获得锁后, 才能返回打断状态</span>                <span class="token keyword">return</span> interrupted<span class="token punctuation">;</span>                        <span class="token punctuation">}</span>                        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">shouldParkAfterFailedAcquire</span><span class="token punctuation">(</span>p<span class="token punctuation">,</span> node<span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> <span class="token function">parkAndCheckInterrupt</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">{</span>                <span class="token comment" spellcheck="true">// 条件二中判断当前线程是否被打断，被打断返回true，设置中断标记为 true，【获取锁后返回】</span>                interrupted <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>              <span class="token punctuation">}</span>                          <span class="token punctuation">}</span>     <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>failed<span class="token punctuation">)</span>            <span class="token function">cancelAcquire</span><span class="token punctuation">(</span>node<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span> <span class="token keyword">private</span> <span class="token keyword">final</span> <span class="token keyword">boolean</span> <span class="token function">parkAndCheckInterrupt</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>         <span class="token comment" spellcheck="true">// 阻塞当前线程，如果打断标记已经是 true, 则 park 会失效</span>     LockSupport<span class="token punctuation">.</span><span class="token function">park</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span><span class="token punctuation">;</span>         <span class="token comment" spellcheck="true">// 判断当前线程是否被打断，清除打断标记，被打断返回true</span>     <span class="token keyword">return</span> Thread<span class="token punctuation">.</span><span class="token function">interrupted</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>2.可打断模式：AbstractQueuedSynchronizer#acquireInterruptibly，<strong>被打断后会直接抛出异常</strong></p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">lockInterruptibly</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">throws</span> InterruptedException <span class="token punctuation">{</span>        sync<span class="token punctuation">.</span><span class="token function">acquireInterruptibly</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token keyword">void</span> <span class="token function">acquireInterruptibly</span><span class="token punctuation">(</span><span class="token keyword">int</span> arg<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 被其他线程打断了直接返回 false</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>Thread<span class="token punctuation">.</span><span class="token function">interrupted</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>        <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">InterruptedException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">tryAcquire</span><span class="token punctuation">(</span>arg<span class="token punctuation">)</span><span class="token punctuation">)</span>        <span class="token comment" spellcheck="true">// 没获取到锁，进入这里</span>        <span class="token function">doAcquireInterruptibly</span><span class="token punctuation">(</span>arg<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">doAcquireInterruptibly</span><span class="token punctuation">(</span><span class="token keyword">int</span> arg<span class="token punctuation">)</span> <span class="token keyword">throws</span> InterruptedException <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 返回封装当前线程的节点</span>    <span class="token keyword">final</span> Node node <span class="token operator">=</span> <span class="token function">addWaiter</span><span class="token punctuation">(</span>Node<span class="token punctuation">.</span>EXCLUSIVE<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">boolean</span> failed <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>    <span class="token keyword">try</span> <span class="token punctuation">{</span>        <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token punctuation">;</span><span class="token punctuation">;</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">//...</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">shouldParkAfterFailedAcquire</span><span class="token punctuation">(</span>p<span class="token punctuation">,</span> node<span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> <span class="token function">parkAndCheckInterrupt</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>                <span class="token comment" spellcheck="true">// 【在 park 过程中如果被 interrupt 会抛出异常】, 而不会再次进入循环获取锁后才完成打断效果</span>                <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">InterruptedException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 抛出异常前会进入这里</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>failed<span class="token punctuation">)</span>            <span class="token comment" spellcheck="true">// 取消当前线程的节点</span>            <span class="token function">cancelAcquire</span><span class="token punctuation">(</span>node<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// 取消节点出队的逻辑</span><span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">cancelAcquire</span><span class="token punctuation">(</span>Node node<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 判空</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>node <span class="token operator">==</span> null<span class="token punctuation">)</span>        <span class="token keyword">return</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 把当前节点封装的 Thread 置为空</span>    node<span class="token punctuation">.</span>thread <span class="token operator">=</span> null<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 获取当前取消的 node 的前驱节点</span>    Node pred <span class="token operator">=</span> node<span class="token punctuation">.</span>prev<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 前驱节点也被取消了，循环找到前面最近的没被取消的节点</span>    <span class="token keyword">while</span> <span class="token punctuation">(</span>pred<span class="token punctuation">.</span>waitStatus <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">)</span>        node<span class="token punctuation">.</span>prev <span class="token operator">=</span> pred <span class="token operator">=</span> pred<span class="token punctuation">.</span>prev<span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 获取前驱节点的后继节点，可能是当前 node，也可能是 waitStatus > 0 的节点</span>    Node predNext <span class="token operator">=</span> pred<span class="token punctuation">.</span>next<span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 把当前节点的状态设置为 【取消状态 1】</span>    node<span class="token punctuation">.</span>waitStatus <span class="token operator">=</span> Node<span class="token punctuation">.</span>CANCELLED<span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 条件成立说明当前节点是尾节点，把当前节点的前驱节点设置为尾节点</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>node <span class="token operator">==</span> tail <span class="token operator">&amp;&amp;</span> <span class="token function">compareAndSetTail</span><span class="token punctuation">(</span>node<span class="token punctuation">,</span> pred<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 把前驱节点的后继节点置空，这里直接把所有的取消节点出队</span>        <span class="token function">compareAndSetNext</span><span class="token punctuation">(</span>pred<span class="token punctuation">,</span> predNext<span class="token punctuation">,</span> null<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 说明当前节点不是 tail 节点</span>        <span class="token keyword">int</span> ws<span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 条件一成立说明当前节点不是 head.next 节点</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>pred <span class="token operator">!=</span> head <span class="token operator">&amp;&amp;</span>            <span class="token comment" spellcheck="true">// 判断前驱节点的状态是不是 -1，不成立说明前驱状态可能是 0 或者刚被其他线程取消排队了</span>            <span class="token punctuation">(</span><span class="token punctuation">(</span>ws <span class="token operator">=</span> pred<span class="token punctuation">.</span>waitStatus<span class="token punctuation">)</span> <span class="token operator">==</span> Node<span class="token punctuation">.</span>SIGNAL <span class="token operator">||</span>             <span class="token comment" spellcheck="true">// 如果状态不是 -1，设置前驱节点的状态为 -1</span>             <span class="token punctuation">(</span>ws <span class="token operator">&lt;=</span> <span class="token number">0</span> <span class="token operator">&amp;&amp;</span> <span class="token function">compareAndSetWaitStatus</span><span class="token punctuation">(</span>pred<span class="token punctuation">,</span> ws<span class="token punctuation">,</span> Node<span class="token punctuation">.</span>SIGNAL<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span>            <span class="token comment" spellcheck="true">// 前驱节点的线程不为null</span>            pred<span class="token punctuation">.</span>thread <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>                        Node next <span class="token operator">=</span> node<span class="token punctuation">.</span>next<span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">// 当前节点的后继节点是正常节点</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span>next <span class="token operator">!=</span> null <span class="token operator">&amp;&amp;</span> next<span class="token punctuation">.</span>waitStatus <span class="token operator">&lt;=</span> <span class="token number">0</span><span class="token punctuation">)</span>                <span class="token comment" spellcheck="true">// 把 前驱节点的后继节点 设置为 当前节点的后继节点，【从队列中删除了当前节点】</span>                <span class="token function">compareAndSetNext</span><span class="token punctuation">(</span>pred<span class="token punctuation">,</span> predNext<span class="token punctuation">,</span> next<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 当前节点是 head.next 节点，唤醒当前节点的后继节点</span>            <span class="token function">unparkSuccessor</span><span class="token punctuation">(</span>node<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        node<span class="token punctuation">.</span>next <span class="token operator">=</span> node<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// help GC</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="❺锁超时原理"><a href="#❺锁超时原理" class="headerlink" title="❺锁超时原理"></a>❺锁超时原理</h3><ul><li><p>成员变量：指定超时限制的阈值，小于该值的线程不会被挂起</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">long</span> spinForTimeoutThreshold <span class="token operator">=</span> 1000L<span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>超时时间设置的小于该值，就会被禁止挂起，因为阻塞在唤醒的成本太高，不如选择自旋空转</p></li><li><p>tryLock()</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">boolean</span> <span class="token function">tryLock</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>       <span class="token comment" spellcheck="true">// 只尝试一次</span>    <span class="token keyword">return</span> sync<span class="token punctuation">.</span><span class="token function">nonfairTryAcquire</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>tryLock(long timeout, TimeUnit unit)</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token keyword">boolean</span> <span class="token function">tryAcquireNanos</span><span class="token punctuation">(</span><span class="token keyword">int</span> arg<span class="token punctuation">,</span> <span class="token keyword">long</span> nanosTimeout<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>Thread<span class="token punctuation">.</span><span class="token function">interrupted</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>                <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">InterruptedException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// tryAcquire 尝试一次</span>    <span class="token keyword">return</span> <span class="token function">tryAcquire</span><span class="token punctuation">(</span>arg<span class="token punctuation">)</span> <span class="token operator">||</span> <span class="token function">doAcquireNanos</span><span class="token punctuation">(</span>arg<span class="token punctuation">,</span> nanosTimeout<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token keyword">protected</span> <span class="token keyword">final</span> <span class="token keyword">boolean</span> <span class="token function">tryAcquire</span><span class="token punctuation">(</span><span class="token keyword">int</span> acquires<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">return</span> <span class="token function">nonfairTryAcquire</span><span class="token punctuation">(</span>acquires<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">boolean</span> <span class="token function">doAcquireNanos</span><span class="token punctuation">(</span><span class="token keyword">int</span> arg<span class="token punctuation">,</span> <span class="token keyword">long</span> nanosTimeout<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>nanosTimeout <span class="token operator">&lt;=</span> 0L<span class="token punctuation">)</span>        <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 获取最后期限的时间戳</span>    <span class="token keyword">final</span> <span class="token keyword">long</span> deadline <span class="token operator">=</span> System<span class="token punctuation">.</span><span class="token function">nanoTime</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> nanosTimeout<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">//...</span>    <span class="token keyword">try</span> <span class="token punctuation">{</span>        <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token punctuation">;</span><span class="token punctuation">;</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">//...</span>            <span class="token comment" spellcheck="true">// 计算还需等待的时间</span>            nanosTimeout <span class="token operator">=</span> deadline <span class="token operator">-</span> System<span class="token punctuation">.</span><span class="token function">nanoTime</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span>nanosTimeout <span class="token operator">&lt;=</span> 0L<span class="token punctuation">)</span><span class="token comment" spellcheck="true">//时间已到     </span>                <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">shouldParkAfterFailedAcquire</span><span class="token punctuation">(</span>p<span class="token punctuation">,</span> node<span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span>                <span class="token comment" spellcheck="true">// 如果 nanosTimeout 大于该值，才有阻塞的意义，否则直接自旋会好点</span>                nanosTimeout <span class="token operator">></span> spinForTimeoutThreshold<span class="token punctuation">)</span>                LockSupport<span class="token punctuation">.</span><span class="token function">parkNanos</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> nanosTimeout<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">// 【被打断会报异常】</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span>Thread<span class="token punctuation">.</span><span class="token function">interrupted</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>                <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">InterruptedException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li></ul><h3 id="❻条件变量原理"><a href="#❻条件变量原理" class="headerlink" title="❻条件变量原理"></a>❻条件变量原理</h3><h4 id="await-x2F-singal流程"><a href="#await-x2F-singal流程" class="headerlink" title="await&#x2F;singal流程"></a>await&#x2F;singal流程</h4><p><strong>await流程</strong></p><p>开始 Thread-0 持有锁，调用 await，进入 ConditionObject 的 addConditionWaiter 流程创建新的 Node 状态为 -2（Node.CONDITION），关联 Thread-0，加入等待队列尾部</p><p><img src="https://img.jwt1399.top/img/202301042053395.png"></p><p>接下来进入 AQS 的 fullyRelease 流程，释放同步器上的锁</p><p><img src="https://img.jwt1399.top/img/202301042053463.png"></p><p>unpark AQS 队列中的下一个节点，竞争锁，假设没有其他竞争线程，那么 Thread-1 竞争成功</p><p><img src="https://img.jwt1399.top/img/202301042053665.png"></p><p>park 阻塞 Thread-0</p><p><img src="https://img.jwt1399.top/img/202301042053656.png"></p><p><strong>singal流程</strong></p><p>假设 Thread-1 要来唤醒 Thread-0</p><p><img src="https://img.jwt1399.top/img/202301042053253.png"></p><p>进入 ConditionObject 的 doSignal 流程，取得等待队列中第一个 Node，即 Thread-0 所在 Node</p><p><img src="https://img.jwt1399.top/img/202301042053575.png"></p><p>执行 transferForSignal 流程，将该 Node 加入 AQS 队列尾部，将 Thread-0 的 waitStatus 改为 0，Thread-3 的waitStatus 改为 -1</p><p><img src="https://img.jwt1399.top/img/202301042053188.png"></p><p>Thread-1 释放锁，进入 unlock 流程</p><h4 id="await"><a href="#await" class="headerlink" title="await"></a>await</h4><p>总体流程是将 await 线程包装成 node 节点放入 ConditionObject 的条件队列，如果被唤醒就将 node 转移到 AQS 的执行阻塞队列，等待获取锁，<strong>每个 Condition 对象都包含一个等待队列</strong></p><ul><li><p>开始 Thread-0 持有锁，调用 await，线程进入 ConditionObject 等待，直到被唤醒或打断，调用 await 方法的线程都是持锁状态的，所以说逻辑里<strong>不存在并发</strong></p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token keyword">void</span> <span class="token function">await</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">throws</span> InterruptedException <span class="token punctuation">{</span>     <span class="token comment" spellcheck="true">// 判断当前线程是否是中断状态，是就直接给个中断异常</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>Thread<span class="token punctuation">.</span><span class="token function">interrupted</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>        <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">InterruptedException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 将调用 await 的线程包装成 Node，添加到条件队列并返回</span>    Node node <span class="token operator">=</span> <span class="token function">addConditionWaiter</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 完全释放节点持有的锁，因为其他线程唤醒当前线程的前提是【持有锁】</span>    <span class="token keyword">int</span> savedState <span class="token operator">=</span> <span class="token function">fullyRelease</span><span class="token punctuation">(</span>node<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 设置打断模式为没有被打断，状态码为 0</span>    <span class="token keyword">int</span> interruptMode <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 如果该节点还没有转移至 AQS 阻塞队列, park 阻塞，等待进入阻塞队列</span>    <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">isOnSyncQueue</span><span class="token punctuation">(</span>node<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        LockSupport<span class="token punctuation">.</span><span class="token function">park</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 如果被打断，退出等待队列，对应的 node 【也会被迁移到阻塞队列】尾部，状态设置为 0</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>interruptMode <span class="token operator">=</span> <span class="token function">checkInterruptWhileWaiting</span><span class="token punctuation">(</span>node<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">!=</span> <span class="token number">0</span><span class="token punctuation">)</span>            <span class="token keyword">break</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">// 逻辑到这说明当前线程退出等待队列，进入【阻塞队列】</span>        <span class="token comment" spellcheck="true">// 尝试枪锁，释放了多少锁就【重新获取多少锁】，获取锁成功判断打断模式</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">acquireQueued</span><span class="token punctuation">(</span>node<span class="token punctuation">,</span> savedState<span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> interruptMode <span class="token operator">!=</span> THROW_IE<span class="token punctuation">)</span>        interruptMode <span class="token operator">=</span> REINTERRUPT<span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// node 在条件队列时 如果被外部线程中断唤醒，会加入到阻塞队列，但是并未设 nextWaiter = null</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>node<span class="token punctuation">.</span>nextWaiter <span class="token operator">!=</span> null<span class="token punctuation">)</span>        <span class="token comment" spellcheck="true">// 清理条件队列内所有已取消的 Node</span>        <span class="token function">unlinkCancelledWaiters</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 条件成立说明挂起期间发生过中断</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>interruptMode <span class="token operator">!=</span> <span class="token number">0</span><span class="token punctuation">)</span>        <span class="token comment" spellcheck="true">// 应用打断模式</span>        <span class="token function">reportInterruptAfterWait</span><span class="token punctuation">(</span>interruptMode<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// 打断模式 - 在退出等待时重新设置打断状态</span><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> REINTERRUPT <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 打断模式 - 在退出等待时抛出异常</span><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> THROW_IE <span class="token operator">=</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p><img src="https://img.jwt1399.top/img/202301042053463.png"></p></li><li><p><strong>创建新的 Node 状态为 -2（Node.CONDITION）</strong>，关联 Thread-0，加入等待队列尾部</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> Node <span class="token function">addConditionWaiter</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 获取当前条件队列的尾节点的引用，保存到局部变量 t 中</span>    Node t <span class="token operator">=</span> lastWaiter<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 当前队列中不是空，并且节点的状态不是 CONDITION（-2），说明当前节点发生了中断</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>t <span class="token operator">!=</span> null <span class="token operator">&amp;&amp;</span> t<span class="token punctuation">.</span>waitStatus <span class="token operator">!=</span> Node<span class="token punctuation">.</span>CONDITION<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 清理条件队列内所有已取消的 Node</span>        <span class="token function">unlinkCancelledWaiters</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 清理完成重新获取 尾节点 的引用</span>        t <span class="token operator">=</span> lastWaiter<span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">// 创建一个关联当前线程的新 node, 设置状态为 CONDITION(-2)，添加至队列尾部</span>    Node node <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Node</span><span class="token punctuation">(</span>Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> Node<span class="token punctuation">.</span>CONDITION<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>t <span class="token operator">==</span> null<span class="token punctuation">)</span>        firstWaiter <span class="token operator">=</span> node<span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 空队列直接放在队首【不用CAS因为执行线程是持锁线程，并发安全】</span>    <span class="token keyword">else</span>        t<span class="token punctuation">.</span>nextWaiter <span class="token operator">=</span> node<span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 非空队列队尾追加</span>    lastWaiter <span class="token operator">=</span> node<span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 更新队尾的引用</span>    <span class="token keyword">return</span> node<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// 清理条件队列内所有已取消（不是CONDITION）的 node，【链表删除的逻辑】</span><span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">unlinkCancelledWaiters</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 从头节点开始遍历【FIFO】</span>    Node t <span class="token operator">=</span> firstWaiter<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 指向正常的 CONDITION 节点</span>    Node trail <span class="token operator">=</span> null<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 等待队列不空</span>    <span class="token keyword">while</span> <span class="token punctuation">(</span>t <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 获取当前节点的后继节点</span>        Node next <span class="token operator">=</span> t<span class="token punctuation">.</span>nextWaiter<span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 判断 t 节点是不是 CONDITION 节点，条件队列内不是 CONDITION 就不是正常的</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>t<span class="token punctuation">.</span>waitStatus <span class="token operator">!=</span> Node<span class="token punctuation">.</span>CONDITION<span class="token punctuation">)</span> <span class="token punctuation">{</span>             <span class="token comment" spellcheck="true">// 不是正常节点，需要 t 与下一个节点断开</span>            t<span class="token punctuation">.</span>nextWaiter <span class="token operator">=</span> null<span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">// 条件成立说明遍历到的节点还未碰到过正常节点</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span>trail <span class="token operator">==</span> null<span class="token punctuation">)</span>                <span class="token comment" spellcheck="true">// 更新 firstWaiter 指针为下个节点</span>                firstWaiter <span class="token operator">=</span> next<span class="token punctuation">;</span>            <span class="token keyword">else</span>                <span class="token comment" spellcheck="true">// 让上一个正常节点指向 当前取消节点的 下一个节点，【删除非正常的节点】</span>                trail<span class="token punctuation">.</span>nextWaiter <span class="token operator">=</span> next<span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">// t 是尾节点了，更新 lastWaiter 指向最后一个正常节点</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span>next <span class="token operator">==</span> null<span class="token punctuation">)</span>                lastWaiter <span class="token operator">=</span> trail<span class="token punctuation">;</span>        <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// trail 指向的是正常节点 </span>            trail <span class="token operator">=</span> t<span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 把 t.next 赋值给 t，循环遍历</span>        t <span class="token operator">=</span> next<span class="token punctuation">;</span>     <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>接下来 Thread-0 进入 AQS 的 fullyRelease 流程，释放同步器上的锁</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// 线程可能重入，需要将 state 全部释放</span><span class="token keyword">final</span> <span class="token keyword">int</span> <span class="token function">fullyRelease</span><span class="token punctuation">(</span>Node node<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 完全释放锁是否成功，false 代表成功</span>    <span class="token keyword">boolean</span> failed <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>    <span class="token keyword">try</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 获取当前线程所持有的 state 值总数</span>        <span class="token keyword">int</span> savedState <span class="token operator">=</span> <span class="token function">getState</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// release -> tryRelease 解锁重入锁</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">release</span><span class="token punctuation">(</span>savedState<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 释放成功</span>            failed <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">// 返回解锁的深度</span>            <span class="token keyword">return</span> savedState<span class="token punctuation">;</span>        <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 解锁失败抛出异常</span>            <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">IllegalMonitorStateException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 没有释放成功，将当前 node 设置为取消状态</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>failed<span class="token punctuation">)</span>            node<span class="token punctuation">.</span>waitStatus <span class="token operator">=</span> Node<span class="token punctuation">.</span>CANCELLED<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>fullyRelease 中会 unpark AQS 队列中的下一个节点竞争锁，假设 Thread-1 竞争成功</p><p><img src="https://img.jwt1399.top/img/202301042053656.png"></p></li><li><p>Thread-0 进入 isOnSyncQueue 逻辑判断节点<strong>是否移动到阻塞队列</strong>，没有就 park 阻塞 Thread-0</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">final</span> <span class="token keyword">boolean</span> <span class="token function">isOnSyncQueue</span><span class="token punctuation">(</span>Node node<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// node 的状态是 CONDITION，signal 方法是先修改状态再迁移，所以前驱节点为空证明还【没有完成迁移】</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>node<span class="token punctuation">.</span>waitStatus <span class="token operator">==</span> Node<span class="token punctuation">.</span>CONDITION <span class="token operator">||</span> node<span class="token punctuation">.</span>prev <span class="token operator">==</span> null<span class="token punctuation">)</span>        <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 说明当前节点已经成功入队到阻塞队列，且当前节点后面已经有其它 node，因为条件队列的 next 指针为 null</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>node<span class="token punctuation">.</span>next <span class="token operator">!=</span> null<span class="token punctuation">)</span>        <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 说明【可能在阻塞队列，但是是尾节点】</span>    <span class="token comment" spellcheck="true">// 从阻塞队列的尾节点开始向前【遍历查找 node】，如果查找到返回 true，查找不到返回 false</span>    <span class="token keyword">return</span> <span class="token function">findNodeFromTail</span><span class="token punctuation">(</span>node<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>await 线程 park 后如果被 unpark 或者被打断，都会进入 checkInterruptWhileWaiting 判断线程是否被打断：<strong>在条件队列被打断的线程需要抛出异常</strong></p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">int</span> <span class="token function">checkInterruptWhileWaiting</span><span class="token punctuation">(</span>Node node<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// Thread.interrupted() 返回当前线程中断标记位，并且重置当前标记位 为 false</span>    <span class="token comment" spellcheck="true">// 如果被中断了，根据是否在条件队列被中断的，设置中断状态码</span>    <span class="token keyword">return</span> Thread<span class="token punctuation">.</span><span class="token function">interrupted</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">?</span><span class="token punctuation">(</span><span class="token function">transferAfterCancelledWait</span><span class="token punctuation">(</span>node<span class="token punctuation">)</span> <span class="token operator">?</span> THROW_IE <span class="token operator">:</span> REINTERRUPT<span class="token punctuation">)</span> <span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// 这个方法只有在线程是被打断唤醒时才会调用</span><span class="token keyword">final</span> <span class="token keyword">boolean</span> <span class="token function">transferAfterCancelledWait</span><span class="token punctuation">(</span>Node node<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 条件成立说明当前node一定是在条件队列内，因为 signal 迁移节点到阻塞队列时，会将节点的状态修改为 0</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">compareAndSetWaitStatus</span><span class="token punctuation">(</span>node<span class="token punctuation">,</span> Node<span class="token punctuation">.</span>CONDITION<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 把【中断唤醒的 node 加入到阻塞队列中】</span>        <span class="token function">enq</span><span class="token punctuation">(</span>node<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 表示是在条件队列内被中断了，设置为 THROW_IE 为 -1</span>        <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">//执行到这里的情况：</span>    <span class="token comment" spellcheck="true">//1.当前node已经被外部线程调用 signal 方法将其迁移到 阻塞队列 内了</span>    <span class="token comment" spellcheck="true">//2.当前node正在被外部线程调用 signal 方法将其迁移至 阻塞队列 进行中状态</span>        <span class="token comment" spellcheck="true">// 如果当前线程还没到阻塞队列，一直释放 CPU</span>    <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">isOnSyncQueue</span><span class="token punctuation">(</span>node<span class="token punctuation">)</span><span class="token punctuation">)</span>        Thread<span class="token punctuation">.</span><span class="token function">yield</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 表示当前节点被中断唤醒时不在条件队列了，设置为 REINTERRUPT 为 1</span>    <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>最后开始处理中断状态：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">reportInterruptAfterWait</span><span class="token punctuation">(</span><span class="token keyword">int</span> interruptMode<span class="token punctuation">)</span> <span class="token keyword">throws</span> InterruptedException <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 条件成立说明【在条件队列内发生过中断，此时 await 方法抛出中断异常】</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>interruptMode <span class="token operator">==</span> THROW_IE<span class="token punctuation">)</span>        <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">InterruptedException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 条件成立说明【在条件队列外发生的中断，此时设置当前线程的中断标记位为 true】</span>    <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>interruptMode <span class="token operator">==</span> REINTERRUPT<span class="token punctuation">)</span>        <span class="token comment" spellcheck="true">// 进行一次自己打断，产生中断的效果</span>        <span class="token function">selfInterrupt</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li></ul><h4 id="signal"><a href="#signal" class="headerlink" title="signal"></a>signal</h4><ul><li><p>假设 Thread-1 要来唤醒 Thread-0，进入 ConditionObject 的 doSignal 流程，<strong>取得等待队列中第一个 Node</strong>，即 Thread-0 所在 Node，必须持有锁才能唤醒, 因此 doSignal 内线程安全</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token keyword">void</span> <span class="token function">signal</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 判断调用 signal 方法的线程是否是独占锁持有线程</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">isHeldExclusively</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>        <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">IllegalMonitorStateException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 获取条件队列中第一个 Node</span>    Node first <span class="token operator">=</span> firstWaiter<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 不为空就将第该节点【迁移到阻塞队列】</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>first <span class="token operator">!=</span> null<span class="token punctuation">)</span>        <span class="token function">doSignal</span><span class="token punctuation">(</span>first<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// 唤醒 - 【将没取消的第一个节点转移至 AQS 队列尾部】</span><span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">doSignal</span><span class="token punctuation">(</span>Node first<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">do</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 成立说明当前节点的下一个节点是 null，当前节点是尾节点了，队列中只有当前一个节点了</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>firstWaiter <span class="token operator">=</span> first<span class="token punctuation">.</span>nextWaiter<span class="token punctuation">)</span> <span class="token operator">==</span> null<span class="token punctuation">)</span>            lastWaiter <span class="token operator">=</span> null<span class="token punctuation">;</span>        first<span class="token punctuation">.</span>nextWaiter <span class="token operator">=</span> null<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 将等待队列中的 Node 转移至 AQS 队列，不成功且还有节点则继续循环</span>    <span class="token punctuation">}</span> <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">transferForSignal</span><span class="token punctuation">(</span>first<span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> <span class="token punctuation">(</span>first <span class="token operator">=</span> firstWaiter<span class="token punctuation">)</span> <span class="token operator">!=</span> null<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">// signalAll() 会调用这个函数，唤醒所有的节点</span><span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">doSignalAll</span><span class="token punctuation">(</span>Node first<span class="token punctuation">)</span> <span class="token punctuation">{</span>    lastWaiter <span class="token operator">=</span> firstWaiter <span class="token operator">=</span> null<span class="token punctuation">;</span>    <span class="token keyword">do</span> <span class="token punctuation">{</span>        Node next <span class="token operator">=</span> first<span class="token punctuation">.</span>nextWaiter<span class="token punctuation">;</span>        first<span class="token punctuation">.</span>nextWaiter <span class="token operator">=</span> null<span class="token punctuation">;</span>        <span class="token function">transferForSignal</span><span class="token punctuation">(</span>first<span class="token punctuation">)</span><span class="token punctuation">;</span>        first <span class="token operator">=</span> next<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 唤醒所有的节点，都放到阻塞队列中</span>    <span class="token punctuation">}</span> <span class="token keyword">while</span> <span class="token punctuation">(</span>first <span class="token operator">!=</span> null<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>执行 transferForSignal，<strong>先将节点的 waitStatus 改为 0，然后加入 AQS 阻塞队列尾部</strong>，将 Thread-3 的 waitStatus 改为 -1</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// 如果节点状态是取消, 返回 false 表示转移失败, 否则转移成功</span><span class="token keyword">final</span> <span class="token keyword">boolean</span> <span class="token function">transferForSignal</span><span class="token punctuation">(</span>Node node<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// CAS 修改当前节点的状态，修改为 0，因为当前节点马上要迁移到阻塞队列了</span>    <span class="token comment" spellcheck="true">// 如果状态已经不是 CONDITION, 说明线程被取消（await 释放全部锁失败）或者被中断（可打断 cancelAcquire）</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">compareAndSetWaitStatus</span><span class="token punctuation">(</span>node<span class="token punctuation">,</span> Node<span class="token punctuation">.</span>CONDITION<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">)</span>        <span class="token comment" spellcheck="true">// 返回函数调用处继续寻找下一个节点</span>        <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 【先改状态，再进行迁移】</span>    <span class="token comment" spellcheck="true">// 将当前 node 入阻塞队列，p 是当前节点在阻塞队列的【前驱节点】</span>    Node p <span class="token operator">=</span> <span class="token function">enq</span><span class="token punctuation">(</span>node<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">int</span> ws <span class="token operator">=</span> p<span class="token punctuation">.</span>waitStatus<span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 如果前驱节点被取消或者不能设置状态为 Node.SIGNAL，就 unpark 取消当前节点线程的阻塞状态, </span>    <span class="token comment" spellcheck="true">// 让 thread-0 线程竞争锁，重新同步状态</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>ws <span class="token operator">></span> <span class="token number">0</span> <span class="token operator">||</span> <span class="token operator">!</span><span class="token function">compareAndSetWaitStatus</span><span class="token punctuation">(</span>p<span class="token punctuation">,</span> ws<span class="token punctuation">,</span> Node<span class="token punctuation">.</span>SIGNAL<span class="token punctuation">)</span><span class="token punctuation">)</span>        LockSupport<span class="token punctuation">.</span><span class="token function">unpark</span><span class="token punctuation">(</span>node<span class="token punctuation">.</span>thread<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><img src="https://img.jwt1399.top/img/202301042053188.png"></p></li><li><p>Thread-1 释放锁，进入 unlock 流程</p></li></ul><h2 id="❸ReentrantReadWriteLock"><a href="#❸ReentrantReadWriteLock" class="headerlink" title="❸ReentrantReadWriteLock"></a>❸ReentrantReadWriteLock</h2><h3 id="简介-1"><a href="#简介-1" class="headerlink" title="简介"></a>简介</h3><p>独占锁：指该锁一次只能被一个线程所持有，对 ReentrantLock 和 Synchronized 而言都是独占锁</p><p>共享锁：指该锁可以被多个线程锁持有</p><p>ReentrantReadWriteLock 其<strong>读锁是共享锁，写锁是独占锁</strong></p><p>作用：多个线程同时读一个资源类没有任何问题，为了满足并发量，读取共享资源应该同时进行，但是如果一个线程想去写共享资源，就不应该再有其它线程可以对该资源进行读或写</p><p>使用规则：</p><ul><li><p>加锁解锁格式：</p><pre class="line-numbers language-java"><code class="language-java">r<span class="token punctuation">.</span><span class="token function">lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">try</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 临界区</span><span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>    r<span class="token punctuation">.</span><span class="token function">unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>读-读能共存、读-写不能共存、写-写不能共存</p></li><li><p>读锁不支持条件变量</p></li><li><p><strong>重入时升级不支持</strong>：持有读锁的情况下去获取写锁会导致获取写锁永久等待，需要先释放读，再去获得写</p></li><li><p><strong>重入时降级支持</strong>：持有写锁的情况下去获取读锁，造成只有当前线程会持有读锁，因为写锁会互斥其他的锁</p><pre class="line-numbers language-java"><code class="language-java">w<span class="token punctuation">.</span><span class="token function">lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">try</span> <span class="token punctuation">{</span>    r<span class="token punctuation">.</span><span class="token function">lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 降级为读锁, 释放写锁, 这样能够让其它线程读取缓存</span>    <span class="token keyword">try</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// ...</span>    <span class="token punctuation">}</span> <span class="token keyword">finally</span><span class="token punctuation">{</span>        w<span class="token punctuation">.</span><span class="token function">unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 要在写锁释放之前获取读锁</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span> <span class="token keyword">finally</span><span class="token punctuation">{</span>    r<span class="token punctuation">.</span><span class="token function">unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li></ul><p>构造方法：</p><ul><li><code>public ReentrantReadWriteLock()</code>：默认构造方法，非公平锁</li><li><code>public ReentrantReadWriteLock(boolean fair)</code>：true 为公平锁</li></ul><p>常用API：</p><ul><li><code>public ReentrantReadWriteLock.ReadLock readLock()</code>：返回读锁</li><li><code>public ReentrantReadWriteLock.WriteLock writeLock()</code>：返回写锁</li><li><code>public void lock()</code>：加锁</li><li><code>public void unlock()</code>：解锁</li><li><code>public boolean tryLock()</code>：尝试获取锁</li></ul><p>提供一个 数据容器类 内部分别使用读锁保护数据的 read() 方法，写锁保护数据的 write() 方法</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">DataContainer</span> <span class="token punctuation">{</span>    <span class="token keyword">private</span> Object data<span class="token punctuation">;</span>    <span class="token keyword">private</span> ReentrantReadWriteLock rw <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ReentrantReadWriteLock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">private</span> ReentrantReadWriteLock<span class="token punctuation">.</span>ReadLock r <span class="token operator">=</span> rw<span class="token punctuation">.</span><span class="token function">readLock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">private</span> ReentrantReadWriteLock<span class="token punctuation">.</span>WriteLock w <span class="token operator">=</span> rw<span class="token punctuation">.</span><span class="token function">writeLock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">public</span> Object <span class="token function">read</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        log<span class="token punctuation">.</span><span class="token function">debug</span><span class="token punctuation">(</span><span class="token string">"获取读锁..."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        r<span class="token punctuation">.</span><span class="token function">lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">try</span> <span class="token punctuation">{</span>            log<span class="token punctuation">.</span><span class="token function">debug</span><span class="token punctuation">(</span><span class="token string">"读取"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">return</span> data<span class="token punctuation">;</span>        <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>            log<span class="token punctuation">.</span><span class="token function">debug</span><span class="token punctuation">(</span><span class="token string">"释放读锁..."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            r<span class="token punctuation">.</span><span class="token function">unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">write</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        log<span class="token punctuation">.</span><span class="token function">debug</span><span class="token punctuation">(</span><span class="token string">"获取写锁..."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        w<span class="token punctuation">.</span><span class="token function">lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">try</span> <span class="token punctuation">{</span>            log<span class="token punctuation">.</span><span class="token function">debug</span><span class="token punctuation">(</span><span class="token string">"写入"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>            log<span class="token punctuation">.</span><span class="token function">debug</span><span class="token punctuation">(</span><span class="token string">"释放写锁..."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            w<span class="token punctuation">.</span><span class="token function">unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>测试读读并发：</p><pre class="line-numbers language-java"><code class="language-java">    DataContainer dataContainer <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">DataContainer</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">></span><span class="token punctuation">{</span>        dataContainer<span class="token punctuation">.</span><span class="token function">read</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">,</span><span class="token string">"t1"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">></span><span class="token punctuation">{</span>        dataContainer<span class="token punctuation">.</span><span class="token function">read</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">,</span><span class="token string">"t2"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token number">14</span><span class="token operator">:</span><span class="token number">05</span><span class="token operator">:</span><span class="token number">14.341</span> c<span class="token punctuation">.</span>DataContainer <span class="token punctuation">[</span>t2<span class="token punctuation">]</span> <span class="token operator">-</span> 获取读锁<span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token number">14</span><span class="token operator">:</span><span class="token number">05</span><span class="token operator">:</span><span class="token number">14.341</span> c<span class="token punctuation">.</span>DataContainer <span class="token punctuation">[</span>t1<span class="token punctuation">]</span> <span class="token operator">-</span> 获取读锁<span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token number">14</span><span class="token operator">:</span><span class="token number">05</span><span class="token operator">:</span><span class="token number">14.345</span> c<span class="token punctuation">.</span>DataContainer <span class="token punctuation">[</span>t1<span class="token punctuation">]</span> <span class="token operator">-</span> 读取<span class="token number">14</span><span class="token operator">:</span><span class="token number">05</span><span class="token operator">:</span><span class="token number">14.345</span> c<span class="token punctuation">.</span>DataContainer <span class="token punctuation">[</span>t2<span class="token punctuation">]</span> <span class="token operator">-</span> 读取<span class="token number">14</span><span class="token operator">:</span><span class="token number">05</span><span class="token operator">:</span><span class="token number">15.365</span> c<span class="token punctuation">.</span>DataContainer <span class="token punctuation">[</span>t2<span class="token punctuation">]</span> <span class="token operator">-</span> 释放读锁<span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token number">14</span><span class="token operator">:</span><span class="token number">05</span><span class="token operator">:</span><span class="token number">15.386</span> c<span class="token punctuation">.</span>DataContainer <span class="token punctuation">[</span>t1<span class="token punctuation">]</span> <span class="token operator">-</span> 释放读锁<span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>测试读写互斥</p><pre class="line-numbers language-java"><code class="language-java">    DataContainer dataContainer <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">DataContainer</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span>        dataContainer<span class="token punctuation">.</span><span class="token function">read</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token string">"t1"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    Thread<span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">100</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span>        dataContainer<span class="token punctuation">.</span><span class="token function">write</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token string">"t2"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token number">14</span><span class="token operator">:</span><span class="token number">04</span><span class="token operator">:</span><span class="token number">21.838</span> c<span class="token punctuation">.</span>DataContainer <span class="token punctuation">[</span>t1<span class="token punctuation">]</span> <span class="token operator">-</span> 获取读锁<span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token number">14</span><span class="token operator">:</span><span class="token number">04</span><span class="token operator">:</span><span class="token number">21.838</span> c<span class="token punctuation">.</span>DataContainer <span class="token punctuation">[</span>t2<span class="token punctuation">]</span> <span class="token operator">-</span> 获取写锁<span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token number">14</span><span class="token operator">:</span><span class="token number">04</span><span class="token operator">:</span><span class="token number">21.841</span> c<span class="token punctuation">.</span>DataContainer <span class="token punctuation">[</span>t2<span class="token punctuation">]</span> <span class="token operator">-</span> 写入<span class="token number">14</span><span class="token operator">:</span><span class="token number">04</span><span class="token operator">:</span><span class="token number">22.843</span> c<span class="token punctuation">.</span>DataContainer <span class="token punctuation">[</span>t2<span class="token punctuation">]</span> <span class="token operator">-</span> 释放写锁<span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token number">14</span><span class="token operator">:</span><span class="token number">04</span><span class="token operator">:</span><span class="token number">22.843</span> c<span class="token punctuation">.</span>DataContainer <span class="token punctuation">[</span>t1<span class="token punctuation">]</span> <span class="token operator">-</span> 读取<span class="token number">14</span><span class="token operator">:</span><span class="token number">04</span><span class="token operator">:</span><span class="token number">23.843</span> c<span class="token punctuation">.</span>DataContainer <span class="token punctuation">[</span>t1<span class="token punctuation">]</span> <span class="token operator">-</span> 释放读锁<span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>写锁-写锁 也是相互阻塞的，这里就不测试了</p><h3 id="实现原理-4"><a href="#实现原理-4" class="headerlink" title="实现原理"></a>实现原理</h3><h4 id="成员属性-3"><a href="#成员属性-3" class="headerlink" title="成员属性"></a>成员属性</h4><p>读写锁用的是同一个 Sycn 同步器，因此等待队列、state 等也是同一个，原理与 ReentrantLock 加锁相比没有特殊之处，不同是<strong>写锁状态占了 state 的低 16 位，而读锁使用的是 state 的高 16 位</strong></p><ul><li><p>读写锁：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">final</span> ReentrantReadWriteLock<span class="token punctuation">.</span>ReadLock readerLock<span class="token punctuation">;</span><span class="token keyword">private</span> <span class="token keyword">final</span> ReentrantReadWriteLock<span class="token punctuation">.</span>WriteLock writerLock<span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre></li><li><p>构造方法：默认是非公平锁，可以指定参数创建公平锁</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token function">ReentrantReadWriteLock</span><span class="token punctuation">(</span><span class="token keyword">boolean</span> fair<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// true 为公平锁</span>    sync <span class="token operator">=</span> fair <span class="token operator">?</span> <span class="token keyword">new</span> <span class="token class-name">FairSync</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">:</span> <span class="token keyword">new</span> <span class="token class-name">NonfairSync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 这两个 lock 共享同一个 sync 实例，都是由 ReentrantReadWriteLock 的 sync 提供同步实现</span>    readerLock <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ReadLock</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    writerLock <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">WriteLock</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li></ul><p>Sync 类的属性：</p><ul><li><p>统计变量：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// 用来移位</span><span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> SHARED_SHIFT   <span class="token operator">=</span> <span class="token number">16</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 高16位的1</span><span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> SHARED_UNIT    <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token number">1</span> <span class="token operator">&lt;&lt;</span> SHARED_SHIFT<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 65535，16个1，代表写锁的最大重入次数</span><span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> MAX_COUNT      <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token number">1</span> <span class="token operator">&lt;&lt;</span> SHARED_SHIFT<span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 低16位掩码：0b 1111 1111 1111 1111，用来获取写锁重入的次数</span><span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> EXCLUSIVE_MASK <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token number">1</span> <span class="token operator">&lt;&lt;</span> SHARED_SHIFT<span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>获取读写锁的次数：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// 获取读写锁的读锁分配的总次数</span><span class="token keyword">static</span> <span class="token keyword">int</span> <span class="token function">sharedCount</span><span class="token punctuation">(</span><span class="token keyword">int</span> c<span class="token punctuation">)</span>    <span class="token punctuation">{</span> <span class="token keyword">return</span> c <span class="token operator">>>></span> SHARED_SHIFT<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token comment" spellcheck="true">// 写锁（独占）锁的重入次数</span><span class="token keyword">static</span> <span class="token keyword">int</span> <span class="token function">exclusiveCount</span><span class="token punctuation">(</span><span class="token keyword">int</span> c<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> c <span class="token operator">&amp;</span> EXCLUSIVE_MASK<span class="token punctuation">;</span> <span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>内部类：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// 记录读锁线程自己的持有读锁的数量（重入次数），因为 state 高16位记录的是全局范围内所有的读线程获取读锁的总量</span><span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">class</span> <span class="token class-name">HoldCounter</span> <span class="token punctuation">{</span>    <span class="token keyword">int</span> count <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// Use id, not reference, to avoid garbage retention</span>    <span class="token keyword">final</span> <span class="token keyword">long</span> tid <span class="token operator">=</span> <span class="token function">getThreadId</span><span class="token punctuation">(</span>Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">// 线程安全的存放线程各自的 HoldCounter 对象</span><span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">class</span> <span class="token class-name">ThreadLocalHoldCounter</span> <span class="token keyword">extends</span> <span class="token class-name">ThreadLocal</span><span class="token operator">&lt;</span>HoldCounter<span class="token operator">></span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> HoldCounter <span class="token function">initialValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">HoldCounter</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>内部类实例：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// 当前线程持有的可重入读锁的数量，计数为 0 时删除</span><span class="token keyword">private</span> <span class="token keyword">transient</span> ThreadLocalHoldCounter readHolds<span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 记录最后一个获取【读锁】线程的 HoldCounter 对象</span><span class="token keyword">private</span> <span class="token keyword">transient</span> HoldCounter cachedHoldCounter<span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>首次获取锁：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// 第一个获取读锁的线程</span><span class="token keyword">private</span> <span class="token keyword">transient</span> Thread firstReader <span class="token operator">=</span> null<span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 记录该线程持有的读锁次数（读锁重入次数）</span><span class="token keyword">private</span> <span class="token keyword">transient</span> <span class="token keyword">int</span> firstReaderHoldCount<span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>Sync 构造方法：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token function">Sync</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    readHolds <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ThreadLocalHoldCounter</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 确保其他线程的数据可见性，state 是 volatile 修饰的变量，重写该值会将线程本地缓存数据【同步至主存】</span>    <span class="token function">setState</span><span class="token punctuation">(</span><span class="token function">getState</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre></li></ul><hr><h4 id="加锁原理"><a href="#加锁原理" class="headerlink" title="加锁原理"></a>加锁原理</h4><ul><li><p>t1 线程：w.lock（<strong>写锁</strong>），成功上锁 state &#x3D; 0_1</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// lock()  -> sync.acquire(1);</span><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    sync<span class="token punctuation">.</span><span class="token function">acquire</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token keyword">void</span> <span class="token function">acquire</span><span class="token punctuation">(</span><span class="token keyword">int</span> arg<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 尝试获得写锁，获得写锁失败，将当前线程关联到一个 Node 对象上, 模式为独占模式 </span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">tryAcquire</span><span class="token punctuation">(</span>arg<span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> <span class="token function">acquireQueued</span><span class="token punctuation">(</span><span class="token function">addWaiter</span><span class="token punctuation">(</span>Node<span class="token punctuation">.</span>EXCLUSIVE<span class="token punctuation">)</span><span class="token punctuation">,</span> arg<span class="token punctuation">)</span><span class="token punctuation">)</span>        <span class="token function">selfInterrupt</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">protected</span> <span class="token keyword">final</span> <span class="token keyword">boolean</span> <span class="token function">tryAcquire</span><span class="token punctuation">(</span><span class="token keyword">int</span> acquires<span class="token punctuation">)</span> <span class="token punctuation">{</span>    Thread current <span class="token operator">=</span> Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">int</span> c <span class="token operator">=</span> <span class="token function">getState</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 获得低 16 位, 代表写锁的 state 计数</span>    <span class="token keyword">int</span> w <span class="token operator">=</span> <span class="token function">exclusiveCount</span><span class="token punctuation">(</span>c<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 说明有读锁或者写锁</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>c <span class="token operator">!=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// c != 0 and w == 0 表示有读锁，【读锁不能升级】，直接返回 false</span>        <span class="token comment" spellcheck="true">// w != 0 说明有写锁，写锁的拥有者不是自己，获取失败</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>w <span class="token operator">==</span> <span class="token number">0</span> <span class="token operator">||</span> current <span class="token operator">!=</span> <span class="token function">getExclusiveOwnerThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>            <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span>                <span class="token comment" spellcheck="true">// 执行到这里只有一种情况：【写锁重入】，所以下面几行代码不存在并发</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>w <span class="token operator">+</span> <span class="token function">exclusiveCount</span><span class="token punctuation">(</span>acquires<span class="token punctuation">)</span> <span class="token operator">></span> MAX_COUNT<span class="token punctuation">)</span>            <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token string">"Maximum lock count exceeded"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 写锁重入, 获得锁成功，没有并发，所以不使用 CAS</span>        <span class="token function">setState</span><span class="token punctuation">(</span>c <span class="token operator">+</span> acquires<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// c == 0，说明没有任何锁，判断写锁是否该阻塞，是 false 就尝试获取锁，失败返回 false</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">writerShouldBlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">||</span> <span class="token operator">!</span><span class="token function">compareAndSetState</span><span class="token punctuation">(</span>c<span class="token punctuation">,</span> c <span class="token operator">+</span> acquires<span class="token punctuation">)</span><span class="token punctuation">)</span>        <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 获得锁成功，设置锁的持有线程为当前线程</span>    <span class="token function">setExclusiveOwnerThread</span><span class="token punctuation">(</span>current<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">// 非公平锁 writerShouldBlock 总是返回 false, 无需阻塞</span><span class="token keyword">final</span> <span class="token keyword">boolean</span> <span class="token function">writerShouldBlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token comment" spellcheck="true">// 公平锁会检查 AQS 队列中是否有前驱节点, 没有(false)才去竞争</span><span class="token keyword">final</span> <span class="token keyword">boolean</span> <span class="token function">writerShouldBlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">return</span> <span class="token function">hasQueuedPredecessors</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>t2 r.lock（<strong>读锁</strong>），进入 tryAcquireShared 流程：</p><ul><li>返回 -1 表示失败</li><li>如果返回 0 表示成功</li><li>返回正数表示还有多少后继节点支持共享模式，读写锁返回 1</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    sync<span class="token punctuation">.</span><span class="token function">acquireShared</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token keyword">void</span> <span class="token function">acquireShared</span><span class="token punctuation">(</span><span class="token keyword">int</span> arg<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// tryAcquireShared 返回负数, 表示获取读锁失败</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">tryAcquireShared</span><span class="token punctuation">(</span>arg<span class="token punctuation">)</span> <span class="token operator">&lt;</span> <span class="token number">0</span><span class="token punctuation">)</span>        <span class="token function">doAcquireShared</span><span class="token punctuation">(</span>arg<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// 尝试以共享模式获取</span><span class="token keyword">protected</span> <span class="token keyword">final</span> <span class="token keyword">int</span> <span class="token function">tryAcquireShared</span><span class="token punctuation">(</span><span class="token keyword">int</span> unused<span class="token punctuation">)</span> <span class="token punctuation">{</span>    Thread current <span class="token operator">=</span> Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">int</span> c <span class="token operator">=</span> <span class="token function">getState</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// exclusiveCount(c) 代表低 16 位, 写锁的 state，成立说明有线程持有写锁</span>    <span class="token comment" spellcheck="true">// 写锁的持有者不是当前线程，则获取读锁失败，【写锁允许降级】</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">exclusiveCount</span><span class="token punctuation">(</span>c<span class="token punctuation">)</span> <span class="token operator">!=</span> <span class="token number">0</span> <span class="token operator">&amp;&amp;</span> <span class="token function">getExclusiveOwnerThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">!=</span> current<span class="token punctuation">)</span>        <span class="token keyword">return</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 高 16 位，代表读锁的 state，共享锁分配出去的总次数</span>    <span class="token keyword">int</span> r <span class="token operator">=</span> <span class="token function">sharedCount</span><span class="token punctuation">(</span>c<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 读锁是否应该阻塞</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">readerShouldBlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span>r <span class="token operator">&lt;</span> MAX_COUNT <span class="token operator">&amp;&amp;</span>        <span class="token function">compareAndSetState</span><span class="token punctuation">(</span>c<span class="token punctuation">,</span> c <span class="token operator">+</span> SHARED_UNIT<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token comment" spellcheck="true">// 尝试增加读锁计数</span>        <span class="token comment" spellcheck="true">// 加锁成功</span>        <span class="token comment" spellcheck="true">// 加锁之前读锁为 0，说明当前线程是第一个读锁线程</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>r <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            firstReader <span class="token operator">=</span> current<span class="token punctuation">;</span>            firstReaderHoldCount <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 第一个读锁线程是自己就发生了读锁重入</span>        <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>firstReader <span class="token operator">==</span> current<span class="token punctuation">)</span> <span class="token punctuation">{</span>            firstReaderHoldCount<span class="token operator">++</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// cachedHoldCounter 设置为当前线程的 holdCounter 对象，即最后一个获取读锁的线程</span>            HoldCounter rh <span class="token operator">=</span> cachedHoldCounter<span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">// 说明还没设置 rh</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span>rh <span class="token operator">==</span> null <span class="token operator">||</span> rh<span class="token punctuation">.</span>tid <span class="token operator">!=</span> <span class="token function">getThreadId</span><span class="token punctuation">(</span>current<span class="token punctuation">)</span><span class="token punctuation">)</span>                <span class="token comment" spellcheck="true">// 获取当前线程的锁重入的对象，赋值给 cachedHoldCounter</span>                cachedHoldCounter <span class="token operator">=</span> rh <span class="token operator">=</span> readHolds<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">// 还没重入</span>            <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>rh<span class="token punctuation">.</span>count <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span>                readHolds<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span>rh<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">// 重入 + 1</span>            rh<span class="token punctuation">.</span>count<span class="token operator">++</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 读锁加锁成功</span>        <span class="token keyword">return</span> <span class="token number">1</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">// 逻辑到这 应该阻塞，或者 cas 加锁失败</span>    <span class="token comment" spellcheck="true">// 会不断尝试 for (;;) 获取读锁, 执行过程中无阻塞</span>    <span class="token keyword">return</span> <span class="token function">fullTryAcquireShared</span><span class="token punctuation">(</span>current<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">// 非公平锁 readerShouldBlock 偏向写锁一些，看 AQS 阻塞队列中第一个节点是否是写锁，是则阻塞，反之不阻塞</span><span class="token comment" spellcheck="true">// 防止一直有读锁线程，导致写锁线程饥饿</span><span class="token comment" spellcheck="true">// true 则该阻塞, false 则不阻塞</span><span class="token keyword">final</span> <span class="token keyword">boolean</span> <span class="token function">readerShouldBlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">return</span> <span class="token function">apparentlyFirstQueuedIsExclusive</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token keyword">final</span> <span class="token keyword">boolean</span> <span class="token function">readerShouldBlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">return</span> <span class="token function">hasQueuedPredecessors</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">final</span> <span class="token keyword">int</span> <span class="token function">fullTryAcquireShared</span><span class="token punctuation">(</span>Thread current<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 当前读锁线程持有的读锁次数对象</span>    HoldCounter rh <span class="token operator">=</span> null<span class="token punctuation">;</span>    <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token punctuation">;</span><span class="token punctuation">;</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">int</span> c <span class="token operator">=</span> <span class="token function">getState</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 说明有线程持有写锁</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">exclusiveCount</span><span class="token punctuation">(</span>c<span class="token punctuation">)</span> <span class="token operator">!=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 写锁不是自己则获取锁失败</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">getExclusiveOwnerThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">!=</span> current<span class="token punctuation">)</span>                <span class="token keyword">return</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">readerShouldBlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 条件成立说明当前线程是 firstReader，当前锁是读忙碌状态，而且当前线程也是读锁重入</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span>firstReader <span class="token operator">==</span> current<span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token comment" spellcheck="true">// assert firstReaderHoldCount > 0;</span>            <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>                <span class="token keyword">if</span> <span class="token punctuation">(</span>rh <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>                    <span class="token comment" spellcheck="true">// 最后一个读锁的 HoldCounter</span>                    rh <span class="token operator">=</span> cachedHoldCounter<span class="token punctuation">;</span>                    <span class="token comment" spellcheck="true">// 说明当前线程也不是最后一个读锁</span>                    <span class="token keyword">if</span> <span class="token punctuation">(</span>rh <span class="token operator">==</span> null <span class="token operator">||</span> rh<span class="token punctuation">.</span>tid <span class="token operator">!=</span> <span class="token function">getThreadId</span><span class="token punctuation">(</span>current<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                        <span class="token comment" spellcheck="true">// 获取当前线程的 HoldCounter</span>                        rh <span class="token operator">=</span> readHolds<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                        <span class="token comment" spellcheck="true">// 条件成立说明 HoldCounter 对象是上一步代码新建的</span>                        <span class="token comment" spellcheck="true">// 当前线程不是锁重入，在 readerShouldBlock() 返回 true 时需要去排队</span>                        <span class="token keyword">if</span> <span class="token punctuation">(</span>rh<span class="token punctuation">.</span>count <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span>                            <span class="token comment" spellcheck="true">// 防止内存泄漏</span>                            readHolds<span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                    <span class="token punctuation">}</span>                <span class="token punctuation">}</span>                <span class="token keyword">if</span> <span class="token punctuation">(</span>rh<span class="token punctuation">.</span>count <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span>                    <span class="token keyword">return</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 越界判断</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">sharedCount</span><span class="token punctuation">(</span>c<span class="token punctuation">)</span> <span class="token operator">==</span> MAX_COUNT<span class="token punctuation">)</span>            <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token string">"Maximum lock count exceeded"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 读锁加锁，条件内的逻辑与 tryAcquireShared 相同</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">compareAndSetState</span><span class="token punctuation">(</span>c<span class="token punctuation">,</span> c <span class="token operator">+</span> SHARED_UNIT<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">sharedCount</span><span class="token punctuation">(</span>c<span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                firstReader <span class="token operator">=</span> current<span class="token punctuation">;</span>                firstReaderHoldCount <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>firstReader <span class="token operator">==</span> current<span class="token punctuation">)</span> <span class="token punctuation">{</span>                firstReaderHoldCount<span class="token operator">++</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>                <span class="token keyword">if</span> <span class="token punctuation">(</span>rh <span class="token operator">==</span> null<span class="token punctuation">)</span>                    rh <span class="token operator">=</span> cachedHoldCounter<span class="token punctuation">;</span>                <span class="token keyword">if</span> <span class="token punctuation">(</span>rh <span class="token operator">==</span> null <span class="token operator">||</span> rh<span class="token punctuation">.</span>tid <span class="token operator">!=</span> <span class="token function">getThreadId</span><span class="token punctuation">(</span>current<span class="token punctuation">)</span><span class="token punctuation">)</span>                    rh <span class="token operator">=</span> readHolds<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>rh<span class="token punctuation">.</span>count <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span>                    readHolds<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span>rh<span class="token punctuation">)</span><span class="token punctuation">;</span>                rh<span class="token punctuation">.</span>count<span class="token operator">++</span><span class="token punctuation">;</span>                cachedHoldCounter <span class="token operator">=</span> rh<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// cache for release</span>            <span class="token punctuation">}</span>            <span class="token keyword">return</span> <span class="token number">1</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>获取读锁失败，进入 sync.doAcquireShared(1) 流程开始阻塞，首先也是调用 addWaiter 添加节点，不同之处在于节点被设置为 Node.SHARED 模式而非 Node.EXCLUSIVE 模式，注意此时 t2 仍处于活跃状态</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">doAcquireShared</span><span class="token punctuation">(</span><span class="token keyword">int</span> arg<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 将当前线程关联到一个 Node 对象上, 模式为共享模式</span>    <span class="token keyword">final</span> Node node <span class="token operator">=</span> <span class="token function">addWaiter</span><span class="token punctuation">(</span>Node<span class="token punctuation">.</span>SHARED<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">boolean</span> failed <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>    <span class="token keyword">try</span> <span class="token punctuation">{</span>        <span class="token keyword">boolean</span> interrupted <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>        <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token punctuation">;</span><span class="token punctuation">;</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 获取前驱节点</span>            <span class="token keyword">final</span> Node p <span class="token operator">=</span> node<span class="token punctuation">.</span><span class="token function">predecessor</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">// 如果前驱节点就头节点就去尝试获取锁</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span>p <span class="token operator">==</span> head<span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token comment" spellcheck="true">// 再一次尝试获取读锁</span>                <span class="token keyword">int</span> r <span class="token operator">=</span> <span class="token function">tryAcquireShared</span><span class="token punctuation">(</span>arg<span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token comment" spellcheck="true">// r >= 0 表示获取成功</span>                <span class="token keyword">if</span> <span class="token punctuation">(</span>r <span class="token operator">>=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                    <span class="token comment" spellcheck="true">//【这里会设置自己为头节点，唤醒相连的后序的共享节点】</span>                    <span class="token function">setHeadAndPropagate</span><span class="token punctuation">(</span>node<span class="token punctuation">,</span> r<span class="token punctuation">)</span><span class="token punctuation">;</span>                    p<span class="token punctuation">.</span>next <span class="token operator">=</span> null<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// help GC</span>                    <span class="token keyword">if</span> <span class="token punctuation">(</span>interrupted<span class="token punctuation">)</span>                        <span class="token function">selfInterrupt</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                    failed <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>                    <span class="token keyword">return</span><span class="token punctuation">;</span>                <span class="token punctuation">}</span>            <span class="token punctuation">}</span>            <span class="token comment" spellcheck="true">// 是否在获取读锁失败时阻塞       park 当前线程</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">shouldParkAfterFailedAcquire</span><span class="token punctuation">(</span>p<span class="token punctuation">,</span> node<span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> <span class="token function">parkAndCheckInterrupt</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>                interrupted <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>failed<span class="token punctuation">)</span>            <span class="token function">cancelAcquire</span><span class="token punctuation">(</span>node<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>如果没有成功，在 doAcquireShared 内 for (;;) 循环一次，shouldParkAfterFailedAcquire 内把前驱节点的 waitStatus 改为 -1，再 for (;;) 循环一次尝试 tryAcquireShared，不成功在 parkAndCheckInterrupt() 处 park</p><img src="https://img.jwt1399.top/img/202301051627804.png" style="zoom: 80%;" /></li><li><p>这种状态下，假设又有 t3 r.lock，t4 w.lock，这期间 t1 仍然持有锁，就变成了下面的样子</p><p><img src="https://img.jwt1399.top/img/202301051627027.png"></p></li></ul><h4 id="解锁原理"><a href="#解锁原理" class="headerlink" title="解锁原理"></a>解锁原理</h4><ul><li><p>t1 w.unlock， 写锁解锁</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 释放锁</span>    sync<span class="token punctuation">.</span><span class="token function">release</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token keyword">boolean</span> <span class="token function">release</span><span class="token punctuation">(</span><span class="token keyword">int</span> arg<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 尝试释放锁</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">tryRelease</span><span class="token punctuation">(</span>arg<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        Node h <span class="token operator">=</span> head<span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 头节点不为空并且不是等待状态不是 0，唤醒后继的非取消节点</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>h <span class="token operator">!=</span> null <span class="token operator">&amp;&amp;</span> h<span class="token punctuation">.</span>waitStatus <span class="token operator">!=</span> <span class="token number">0</span><span class="token punctuation">)</span>            <span class="token function">unparkSuccessor</span><span class="token punctuation">(</span>h<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token keyword">protected</span> <span class="token keyword">final</span> <span class="token keyword">boolean</span> <span class="token function">tryRelease</span><span class="token punctuation">(</span><span class="token keyword">int</span> releases<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">isHeldExclusively</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>        <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">IllegalMonitorStateException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">int</span> nextc <span class="token operator">=</span> <span class="token function">getState</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span> releases<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 因为可重入的原因, 写锁计数为 0, 才算释放成功</span>    <span class="token keyword">boolean</span> free <span class="token operator">=</span> <span class="token function">exclusiveCount</span><span class="token punctuation">(</span>nextc<span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>free<span class="token punctuation">)</span>        <span class="token function">setExclusiveOwnerThread</span><span class="token punctuation">(</span>null<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token function">setState</span><span class="token punctuation">(</span>nextc<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> free<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>唤醒流程 sync.unparkSuccessor，这时 t2 在 doAcquireShared 的 parkAndCheckInterrupt() 处恢复运行，继续循环，执行 tryAcquireShared 成功则让读锁计数加一</p></li><li><p>接下来 t2 调用 setHeadAndPropagate(node, 1)，它原本所在节点被置为头节点；还会检查下一个节点是否是 shared，如果是则调用 doReleaseShared() 将 head 的状态从 -1 改为 0 并唤醒下一个节点，这时 t3 在 doAcquireShared 内 parkAndCheckInterrupt() 处恢复运行，<strong>唤醒连续的所有的共享节点</strong></p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">setHeadAndPropagate</span><span class="token punctuation">(</span>Node node<span class="token punctuation">,</span> <span class="token keyword">int</span> propagate<span class="token punctuation">)</span> <span class="token punctuation">{</span>    Node h <span class="token operator">=</span> head<span class="token punctuation">;</span>     <span class="token comment" spellcheck="true">// 设置自己为 head 节点</span>    <span class="token function">setHead</span><span class="token punctuation">(</span>node<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// propagate 表示有共享资源（例如共享读锁或信号量），为 0 就没有资源</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>propagate <span class="token operator">></span> <span class="token number">0</span> <span class="token operator">||</span> h <span class="token operator">==</span> null <span class="token operator">||</span> h<span class="token punctuation">.</span>waitStatus <span class="token operator">&lt;</span> <span class="token number">0</span> <span class="token operator">||</span>        <span class="token punctuation">(</span>h <span class="token operator">=</span> head<span class="token punctuation">)</span> <span class="token operator">==</span> null <span class="token operator">||</span> h<span class="token punctuation">.</span>waitStatus <span class="token operator">&lt;</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 获取下一个节点</span>        Node s <span class="token operator">=</span> node<span class="token punctuation">.</span>next<span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 如果当前是最后一个节点，或者下一个节点是【等待共享读锁的节点】</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>s <span class="token operator">==</span> null <span class="token operator">||</span> s<span class="token punctuation">.</span><span class="token function">isShared</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>            <span class="token comment" spellcheck="true">// 唤醒后继节点</span>            <span class="token function">doReleaseShared</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">doReleaseShared</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 如果 head.waitStatus == Node.SIGNAL ==> 0 成功, 下一个节点 unpark</span>    <span class="token comment" spellcheck="true">// 如果 head.waitStatus == 0 ==> Node.PROPAGATE</span>    <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token punctuation">;</span><span class="token punctuation">;</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        Node h <span class="token operator">=</span> head<span class="token punctuation">;</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>h <span class="token operator">!=</span> null <span class="token operator">&amp;&amp;</span> h <span class="token operator">!=</span> tail<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token keyword">int</span> ws <span class="token operator">=</span> h<span class="token punctuation">.</span>waitStatus<span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">// SIGNAL 唤醒后继</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span>ws <span class="token operator">==</span> Node<span class="token punctuation">.</span>SIGNAL<span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token comment" spellcheck="true">// 因为读锁共享，如果其它线程也在释放读锁，那么需要将 waitStatus 先改为 0</span>                <span class="token comment" spellcheck="true">// 防止 unparkSuccessor 被多次执行</span>                <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">compareAndSetWaitStatus</span><span class="token punctuation">(</span>h<span class="token punctuation">,</span> Node<span class="token punctuation">.</span>SIGNAL<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">)</span>                    <span class="token keyword">continue</span><span class="token punctuation">;</span>                  <span class="token comment" spellcheck="true">// 唤醒后继节点</span>                <span class="token function">unparkSuccessor</span><span class="token punctuation">(</span>h<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>            <span class="token comment" spellcheck="true">// 如果已经是 0 了，改为 -3，用来解决传播性</span>            <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>ws <span class="token operator">==</span> <span class="token number">0</span> <span class="token operator">&amp;&amp;</span> <span class="token operator">!</span><span class="token function">compareAndSetWaitStatus</span><span class="token punctuation">(</span>h<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> Node<span class="token punctuation">.</span>PROPAGATE<span class="token punctuation">)</span><span class="token punctuation">)</span>                <span class="token keyword">continue</span><span class="token punctuation">;</span>                        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 条件不成立说明被唤醒的节点非常积极，直接将自己设置为了新的 head，</span>        <span class="token comment" spellcheck="true">// 此时唤醒它的节点（前驱）执行 h == head 不成立，所以不会跳出循环，会继续唤醒新的 head 节点的后继节点</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>h <span class="token operator">==</span> head<span class="token punctuation">)</span>                               <span class="token keyword">break</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><img src="https://img.jwt1399.top/img/202301051627915.png" style="zoom: 67%;" /></li><li><p>下一个节点不是 shared 了，因此不会继续唤醒 t4 所在节点</p></li><li><p>t2 读锁解锁，进入 sync.releaseShared(1) 中，调用 tryReleaseShared(1) 让计数减一，但计数还不为零，t3 同样让计数减一，计数为零，进入doReleaseShared() 将头节点从 -1 改为 0 并唤醒下一个节点</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    sync<span class="token punctuation">.</span><span class="token function">releaseShared</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token keyword">boolean</span> <span class="token function">releaseShared</span><span class="token punctuation">(</span><span class="token keyword">int</span> arg<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">tryReleaseShared</span><span class="token punctuation">(</span>arg<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token function">doReleaseShared</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">protected</span> <span class="token keyword">final</span> <span class="token keyword">boolean</span> <span class="token function">tryReleaseShared</span><span class="token punctuation">(</span><span class="token keyword">int</span> unused<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token punctuation">;</span><span class="token punctuation">;</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">int</span> c <span class="token operator">=</span> <span class="token function">getState</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">int</span> nextc <span class="token operator">=</span> c <span class="token operator">-</span> SHARED_UNIT<span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 读锁的计数不会影响其它获取读锁线程, 但会影响其它获取写锁线程，计数为 0 才是真正释放</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">compareAndSetState</span><span class="token punctuation">(</span>c<span class="token punctuation">,</span> nextc<span class="token punctuation">)</span><span class="token punctuation">)</span>            <span class="token comment" spellcheck="true">// 返回是否已经完全释放了 </span>            <span class="token keyword">return</span> nextc <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>t4 在 acquireQueued 中 parkAndCheckInterrupt 处恢复运行，再次 for (;;) 这次自己是头节点的临节点，并且没有其他节点竞争，tryAcquire(1) 成功，修改头结点，流程结束</p><img src="https://img.jwt1399.top/img/202301051627580.png" style="zoom: 67%;" /></li></ul><h2 id="❹StampedLock"><a href="#❹StampedLock" class="headerlink" title="❹StampedLock"></a>❹StampedLock</h2><p>读写锁，该类自 JDK 8 加入，是为了进一步优化读性能，它的特点是在使用读锁、写锁时都必须配合【戳】使用</p><p>特点：</p><ul><li><p>在使用读锁、写锁时都必须配合戳使用</p></li><li><p>StampedLock 不支持条件变量</p></li><li><p>StampedLock <strong>不支持重入</strong></p></li></ul><p>基本用法</p><ul><li><p>加解读锁：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">long</span> stamp <span class="token operator">=</span> lock<span class="token punctuation">.</span><span class="token function">readLock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>lock<span class="token punctuation">.</span><span class="token function">unlockRead</span><span class="token punctuation">(</span>stamp<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 类似于 unpark，解指定的锁</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre></li><li><p>加解写锁：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">long</span> stamp <span class="token operator">=</span> lock<span class="token punctuation">.</span><span class="token function">writeLock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>lock<span class="token punctuation">.</span><span class="token function">unlockWrite</span><span class="token punctuation">(</span>stamp<span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre></li><li><p>乐观读，StampedLock 支持 <code>tryOptimisticRead()</code> 方法，读取完毕后做一次<strong>戳校验</strong>，如果校验通过，表示这期间没有其他线程的写操作，数据可以安全使用，如果校验没通过，需要重新获取读锁，保证数据一致性</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">long</span> stamp <span class="token operator">=</span> lock<span class="token punctuation">.</span><span class="token function">tryOptimisticRead</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 验戳</span><span class="token keyword">if</span><span class="token punctuation">(</span><span class="token operator">!</span>lock<span class="token punctuation">.</span><span class="token function">validate</span><span class="token punctuation">(</span>stamp<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 锁升级</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre></li></ul><p>提供一个数据容器类内部分别使用读锁保护数据的 read() 方法，写锁保护数据的 write() 方法：</p><ul><li>读-读可以优化</li><li>读-写优化读，补加读锁</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token keyword">throws</span> InterruptedException <span class="token punctuation">{</span>    DataContainerStamped dataContainer <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">DataContainerStamped</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span>        dataContainer<span class="token punctuation">.</span><span class="token function">read</span><span class="token punctuation">(</span><span class="token number">1000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">,</span><span class="token string">"t1"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    Thread<span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">500</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span>        dataContainer<span class="token punctuation">.</span><span class="token function">write</span><span class="token punctuation">(</span><span class="token number">1000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">,</span><span class="token string">"t2"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token keyword">class</span> <span class="token class-name">DataContainerStamped</span> <span class="token punctuation">{</span>    <span class="token keyword">private</span> <span class="token keyword">int</span> data<span class="token punctuation">;</span>    <span class="token keyword">private</span> <span class="token keyword">final</span> StampedLock lock <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">StampedLock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">read</span><span class="token punctuation">(</span><span class="token keyword">int</span> readTime<span class="token punctuation">)</span> <span class="token keyword">throws</span> InterruptedException <span class="token punctuation">{</span>        <span class="token keyword">long</span> stamp <span class="token operator">=</span> lock<span class="token punctuation">.</span><span class="token function">tryOptimisticRead</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">" optimistic read locking"</span> <span class="token operator">+</span> stamp<span class="token punctuation">)</span><span class="token punctuation">;</span>        Thread<span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span>readTime<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 戳有效，直接返回数据</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>lock<span class="token punctuation">.</span><span class="token function">validate</span><span class="token punctuation">(</span>stamp<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token function">Sout</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">" optimistic read finish..."</span> <span class="token operator">+</span> stamp<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">return</span> data<span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 说明其他线程更改了戳，需要锁升级了，从乐观读升级到读锁</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">" updating to read lock"</span> <span class="token operator">+</span> stamp<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">try</span> <span class="token punctuation">{</span>            stamp <span class="token operator">=</span> lock<span class="token punctuation">.</span><span class="token function">readLock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">" read lock"</span> <span class="token operator">+</span> stamp<span class="token punctuation">)</span><span class="token punctuation">;</span>            Thread<span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span>readTime<span class="token punctuation">)</span><span class="token punctuation">;</span>            System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">" read finish..."</span> <span class="token operator">+</span> stamp<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">return</span> data<span class="token punctuation">;</span>        <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>            System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">" read unlock "</span> <span class="token operator">+</span>  stamp<span class="token punctuation">)</span><span class="token punctuation">;</span>            lock<span class="token punctuation">.</span><span class="token function">unlockRead</span><span class="token punctuation">(</span>stamp<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">write</span><span class="token punctuation">(</span><span class="token keyword">int</span> newData<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">long</span> stamp <span class="token operator">=</span> lock<span class="token punctuation">.</span><span class="token function">writeLock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">" write lock "</span> <span class="token operator">+</span> stamp<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">try</span> <span class="token punctuation">{</span>            Thread<span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">2000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">this</span><span class="token punctuation">.</span>data <span class="token operator">=</span> newData<span class="token punctuation">;</span>        <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">InterruptedException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span>            e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>            System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">" write unlock "</span> <span class="token operator">+</span> stamp<span class="token punctuation">)</span><span class="token punctuation">;</span>            lock<span class="token punctuation">.</span><span class="token function">unlockWrite</span><span class="token punctuation">(</span>stamp<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h1 id="⑦并发工具类"><a href="#⑦并发工具类" class="headerlink" title="⑦并发工具类"></a>⑦并发工具类</h1><h2 id="❶Semaphore"><a href="#❶Semaphore" class="headerlink" title="❶Semaphore"></a>❶Semaphore</h2><h3 id="基本使用-6"><a href="#基本使用-6" class="headerlink" title="基本使用"></a>基本使用</h3><p>Semaphore（信号量）用来限制能同时访问共享资源的线程上限，非重入锁</p><p>构造方法：</p><ul><li><code>public Semaphore(int permits)</code>：permits 表示许可线程的数量（state）</li><li><code>public Semaphore(int permits, boolean fair)</code>：fair 表示公平性，如果设为 true，下次执行的线程会是等待最久的线程</li></ul><p>常用API：</p><ul><li><code>public void acquire()</code>：表示获取许可</li><li><code>public void release()</code>：表示释放许可，acquire() 和 release() 方法之间的代码为同步代码</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 1.创建Semaphore对象</span>    Semaphore semaphore <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Semaphore</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 2. 10个线程同时运行</span>    <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> <span class="token number">10</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span>            <span class="token keyword">try</span> <span class="token punctuation">{</span>                <span class="token comment" spellcheck="true">// 3. 获取许可</span>                semaphore<span class="token punctuation">.</span><span class="token function">acquire</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token function">sout</span><span class="token punctuation">(</span>Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">" running..."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                Thread<span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">1000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token function">sout</span><span class="token punctuation">(</span>Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">" end..."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">InterruptedException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span>                e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>                <span class="token comment" spellcheck="true">// 4. 释放许可</span>                semaphore<span class="token punctuation">.</span><span class="token function">release</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="实现原理-5"><a href="#实现原理-5" class="headerlink" title="实现原理"></a>实现原理</h3><p>加锁流程：</p><ul><li><p>Semaphore 的 permits（state）为 3，这时 5 个线程来获取资源</p><pre class="line-numbers language-java"><code class="language-java"><span class="token function">Sync</span><span class="token punctuation">(</span><span class="token keyword">int</span> permits<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token function">setState</span><span class="token punctuation">(</span>permits<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>假设其中 Thread-1，Thread-2，Thread-4 CAS 竞争成功，permits 变为 0，而 Thread-0 和 Thread-3 竞争失败，进入 AQS 队列park 阻塞</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// acquire() -> sync.acquireSharedInterruptibly(1)，可中断</span><span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token keyword">void</span> <span class="token function">acquireSharedInterruptibly</span><span class="token punctuation">(</span><span class="token keyword">int</span> arg<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>Thread<span class="token punctuation">.</span><span class="token function">interrupted</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>        <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">InterruptedException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 尝试获取通行证，获取成功返回 >= 0的值</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">tryAcquireShared</span><span class="token punctuation">(</span>arg<span class="token punctuation">)</span> <span class="token operator">&lt;</span> <span class="token number">0</span><span class="token punctuation">)</span>        <span class="token comment" spellcheck="true">// 获取许可证失败，进入阻塞</span>        <span class="token function">doAcquireSharedInterruptibly</span><span class="token punctuation">(</span>arg<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">// tryAcquireShared() -> nonfairTryAcquireShared()</span><span class="token comment" spellcheck="true">// 非公平，公平锁会在循环内 hasQueuedPredecessors()方法判断阻塞队列是否有临头节点(第二个节点)</span><span class="token keyword">final</span> <span class="token keyword">int</span> <span class="token function">nonfairTryAcquireShared</span><span class="token punctuation">(</span><span class="token keyword">int</span> acquires<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token punctuation">;</span><span class="token punctuation">;</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 获取 state ，state 这里【表示通行证】</span>        <span class="token keyword">int</span> available <span class="token operator">=</span> <span class="token function">getState</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 计算当前线程获取通行证完成之后，通行证还剩余数量</span>        <span class="token keyword">int</span> remaining <span class="token operator">=</span> available <span class="token operator">-</span> acquires<span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 如果许可已经用完, 返回负数, 表示获取失败,</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>remaining <span class="token operator">&lt;</span> <span class="token number">0</span> <span class="token operator">||</span>            <span class="token comment" spellcheck="true">// 许可证足够分配的，如果 cas 重试成功, 返回正数, 表示获取成功</span>            <span class="token function">compareAndSetState</span><span class="token punctuation">(</span>available<span class="token punctuation">,</span> remaining<span class="token punctuation">)</span><span class="token punctuation">)</span>            <span class="token keyword">return</span> remaining<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">doAcquireSharedInterruptibly</span><span class="token punctuation">(</span><span class="token keyword">int</span> arg<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 将调用 Semaphore.aquire 方法的线程，包装成 node 加入到 AQS 的阻塞队列中</span>    <span class="token keyword">final</span> Node node <span class="token operator">=</span> <span class="token function">addWaiter</span><span class="token punctuation">(</span>Node<span class="token punctuation">.</span>SHARED<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 获取标记</span>    <span class="token keyword">boolean</span> failed <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>    <span class="token keyword">try</span> <span class="token punctuation">{</span>        <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token punctuation">;</span><span class="token punctuation">;</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token keyword">final</span> Node p <span class="token operator">=</span> node<span class="token punctuation">.</span><span class="token function">predecessor</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">// 前驱节点是头节点可以再次获取许可</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span>p <span class="token operator">==</span> head<span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token comment" spellcheck="true">// 再次尝试获取许可，【返回剩余的许可证数量】</span>                <span class="token keyword">int</span> r <span class="token operator">=</span> <span class="token function">tryAcquireShared</span><span class="token punctuation">(</span>arg<span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token keyword">if</span> <span class="token punctuation">(</span>r <span class="token operator">>=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                    <span class="token comment" spellcheck="true">// 成功后本线程出队（AQS）, 所在 Node设置为 head</span>                    <span class="token comment" spellcheck="true">// r 表示【可用资源数】, 为 0 则不会继续传播</span>                    <span class="token function">setHeadAndPropagate</span><span class="token punctuation">(</span>node<span class="token punctuation">,</span> r<span class="token punctuation">)</span><span class="token punctuation">;</span>                     p<span class="token punctuation">.</span>next <span class="token operator">=</span> null<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// help GC</span>                    failed <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>                    <span class="token keyword">return</span><span class="token punctuation">;</span>                <span class="token punctuation">}</span>            <span class="token punctuation">}</span>            <span class="token comment" spellcheck="true">// 不成功, 设置上一个节点 waitStatus = Node.SIGNAL, 下轮进入 park 阻塞</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">shouldParkAfterFailedAcquire</span><span class="token punctuation">(</span>p<span class="token punctuation">,</span> node<span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> <span class="token function">parkAndCheckInterrupt</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>                <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">InterruptedException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 被打断后进入该逻辑</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>failed<span class="token punctuation">)</span>            <span class="token function">cancelAcquire</span><span class="token punctuation">(</span>node<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">setHeadAndPropagate</span><span class="token punctuation">(</span>Node node<span class="token punctuation">,</span> <span class="token keyword">int</span> propagate<span class="token punctuation">)</span> <span class="token punctuation">{</span>        Node h <span class="token operator">=</span> head<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 设置自己为 head 节点</span>    <span class="token function">setHead</span><span class="token punctuation">(</span>node<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// propagate 表示有【共享资源】（例如共享读锁或信号量）</span>    <span class="token comment" spellcheck="true">// head waitStatus == Node.SIGNAL 或 Node.PROPAGATE，doReleaseShared 函数中设置的</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>propagate <span class="token operator">></span> <span class="token number">0</span> <span class="token operator">||</span> h <span class="token operator">==</span> null <span class="token operator">||</span> h<span class="token punctuation">.</span>waitStatus <span class="token operator">&lt;</span> <span class="token number">0</span> <span class="token operator">||</span>        <span class="token punctuation">(</span>h <span class="token operator">=</span> head<span class="token punctuation">)</span> <span class="token operator">==</span> null <span class="token operator">||</span> h<span class="token punctuation">.</span>waitStatus <span class="token operator">&lt;</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        Node s <span class="token operator">=</span> node<span class="token punctuation">.</span>next<span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 如果是最后一个节点或者是等待共享读锁的节点，做一次唤醒</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>s <span class="token operator">==</span> null <span class="token operator">||</span> s<span class="token punctuation">.</span><span class="token function">isShared</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>            <span class="token function">doReleaseShared</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><img src="https://img.jwt1399.top/img/202301051627469.png"></p></li><li><p>这时 Thread-4 释放了 permits，状态如下</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// release() -> releaseShared()</span><span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token keyword">boolean</span> <span class="token function">releaseShared</span><span class="token punctuation">(</span><span class="token keyword">int</span> arg<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 尝试释放锁</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">tryReleaseShared</span><span class="token punctuation">(</span>arg<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token function">doReleaseShared</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>        <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token keyword">protected</span> <span class="token keyword">final</span> <span class="token keyword">boolean</span> <span class="token function">tryReleaseShared</span><span class="token punctuation">(</span><span class="token keyword">int</span> releases<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token punctuation">;</span><span class="token punctuation">;</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 获取当前锁资源的可用许可证数量</span>        <span class="token keyword">int</span> current <span class="token operator">=</span> <span class="token function">getState</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">int</span> next <span class="token operator">=</span> current <span class="token operator">+</span> releases<span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 索引越界判断</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>next <span class="token operator">&lt;</span> current<span class="token punctuation">)</span>                        <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token string">"Maximum permit count exceeded"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token comment" spellcheck="true">// 释放锁</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">compareAndSetState</span><span class="token punctuation">(</span>current<span class="token punctuation">,</span> next<span class="token punctuation">)</span><span class="token punctuation">)</span>                        <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">doReleaseShared</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// PROPAGATE 详解    </span>    <span class="token comment" spellcheck="true">// 如果 head.waitStatus == Node.SIGNAL ==> 0 成功, 下一个节点 unpark</span>    <span class="token comment" spellcheck="true">// 如果 head.waitStatus == 0 ==> Node.PROPAGATE</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><img src="https://img.jwt1399.top/img/202301051627529.png"></p></li><li><p>接下来 Thread-0 竞争成功，permits 再次设置为 0，设置自己为 head 节点，并且 unpark 接下来的共享状态的 Thread-3 节点，但由于 permits 是 0，因此 Thread-3 在尝试不成功后再次进入 park 状态</p></li></ul><h2 id="❷CountDownLatch"><a href="#❷CountDownLatch" class="headerlink" title="❷CountDownLatch"></a>❷CountDownLatch</h2><h3 id="基本使用-7"><a href="#基本使用-7" class="headerlink" title="基本使用"></a>基本使用</h3><p>CountDownLatch：计数器，用来进行线程同步协作，<strong>等待所有线程完成</strong></p><p>构造器：</p><ul><li><code>public CountDownLatch(int count)</code>：初始化，唤醒需要 down 几步</li></ul><p>常用API：</p><ul><li><code>public void await() </code>：让当前线程等待，必须 down 完初始化的数字才可以被唤醒，否则进入无限等待</li><li><code>public void countDown()</code>：计数器进行减 1（down 1）</li></ul><p>应用：同步等待多个 Rest 远程调用结束</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// LOL 10人进入游戏倒计时</span><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token keyword">throws</span> InterruptedException <span class="token punctuation">{</span>    CountDownLatch latch <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">CountDownLatch</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    ExecutorService service <span class="token operator">=</span> Executors<span class="token punctuation">.</span><span class="token function">newFixedThreadPool</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    String<span class="token punctuation">[</span><span class="token punctuation">]</span> all <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">String</span><span class="token punctuation">[</span><span class="token number">10</span><span class="token punctuation">]</span><span class="token punctuation">;</span>    Random random <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Random</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> j <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> j <span class="token operator">&lt;</span> <span class="token number">10</span><span class="token punctuation">;</span> j<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">int</span> k <span class="token operator">=</span> j<span class="token punctuation">;</span><span class="token comment" spellcheck="true">//常量</span>        service<span class="token punctuation">.</span><span class="token function">submit</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span>            <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;=</span> <span class="token number">100</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                Thread<span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span>random<span class="token punctuation">.</span><span class="token function">nextInt</span><span class="token punctuation">(</span><span class="token number">100</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//随机休眠</span>                all<span class="token punctuation">[</span>k<span class="token punctuation">]</span> <span class="token operator">=</span> i <span class="token operator">+</span> <span class="token string">"%"</span><span class="token punctuation">;</span>                System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">print</span><span class="token punctuation">(</span><span class="token string">"\r"</span> <span class="token operator">+</span> Arrays<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span>all<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// \r代表覆盖</span>            <span class="token punctuation">}</span>            latch<span class="token punctuation">.</span><span class="token function">countDown</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    latch<span class="token punctuation">.</span><span class="token function">await</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"\n游戏开始"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    service<span class="token punctuation">.</span><span class="token function">shutdown</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token operator">/</span><span class="token operator">*</span><span class="token punctuation">[</span><span class="token number">100</span><span class="token operator">%</span><span class="token punctuation">,</span> <span class="token number">100</span><span class="token operator">%</span><span class="token punctuation">,</span> <span class="token number">100</span><span class="token operator">%</span><span class="token punctuation">,</span> <span class="token number">100</span><span class="token operator">%</span><span class="token punctuation">,</span> <span class="token number">100</span><span class="token operator">%</span><span class="token punctuation">,</span> <span class="token number">100</span><span class="token operator">%</span><span class="token punctuation">,</span> <span class="token number">100</span><span class="token operator">%</span><span class="token punctuation">,</span> <span class="token number">100</span><span class="token operator">%</span><span class="token punctuation">,</span> <span class="token number">100</span><span class="token operator">%</span><span class="token punctuation">,</span> <span class="token number">100</span><span class="token operator">%</span><span class="token punctuation">]</span>游戏开始<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="实现原理-6"><a href="#实现原理-6" class="headerlink" title="实现原理"></a>实现原理</h3><p>阻塞等待：</p><ul><li><p>线程调用 await() 等待其他线程完成任务：支持打断</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">await</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">throws</span> InterruptedException <span class="token punctuation">{</span>    sync<span class="token punctuation">.</span><span class="token function">acquireSharedInterruptibly</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">// AbstractQueuedSynchronizer#acquireSharedInterruptibly</span><span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token keyword">void</span> <span class="token function">acquireSharedInterruptibly</span><span class="token punctuation">(</span><span class="token keyword">int</span> arg<span class="token punctuation">)</span> <span class="token keyword">throws</span> InterruptedException <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 判断线程是否被打断，抛出打断异常</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>Thread<span class="token punctuation">.</span><span class="token function">interrupted</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>        <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">InterruptedException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 尝试获取共享锁，条件成立说明 state > 0，此时线程入队阻塞等待，等待其他线程获取共享资源</span>    <span class="token comment" spellcheck="true">// 条件不成立说明 state = 0，此时不需要阻塞线程，直接结束函数调用</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">tryAcquireShared</span><span class="token punctuation">(</span>arg<span class="token punctuation">)</span> <span class="token operator">&lt;</span> <span class="token number">0</span><span class="token punctuation">)</span>        <span class="token function">doAcquireSharedInterruptibly</span><span class="token punctuation">(</span>arg<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">// CountDownLatch.Sync#tryAcquireShared</span><span class="token keyword">protected</span> <span class="token keyword">int</span> <span class="token function">tryAcquireShared</span><span class="token punctuation">(</span><span class="token keyword">int</span> acquires<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token function">getState</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token number">1</span> <span class="token operator">:</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>线程进入 AbstractQueuedSynchronizer#doAcquireSharedInterruptibly 函数阻塞挂起，等待 latch 变为 0：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">doAcquireSharedInterruptibly</span><span class="token punctuation">(</span><span class="token keyword">int</span> arg<span class="token punctuation">)</span> <span class="token keyword">throws</span> InterruptedException <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 将调用latch.await()方法的线程 包装成 SHARED 类型的 node 加入到 AQS 的阻塞队列中</span>    <span class="token keyword">final</span> Node node <span class="token operator">=</span> <span class="token function">addWaiter</span><span class="token punctuation">(</span>Node<span class="token punctuation">.</span>SHARED<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">boolean</span> failed <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>    <span class="token keyword">try</span> <span class="token punctuation">{</span>        <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token punctuation">;</span><span class="token punctuation">;</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 获取当前节点的前驱节点</span>            <span class="token keyword">final</span> Node p <span class="token operator">=</span> node<span class="token punctuation">.</span><span class="token function">predecessor</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">// 前驱节点时头节点就可以尝试获取锁</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span>p <span class="token operator">==</span> head<span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token comment" spellcheck="true">// 再次尝试获取锁，获取成功返回 1</span>                <span class="token keyword">int</span> r <span class="token operator">=</span> <span class="token function">tryAcquireShared</span><span class="token punctuation">(</span>arg<span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token keyword">if</span> <span class="token punctuation">(</span>r <span class="token operator">>=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                    <span class="token comment" spellcheck="true">// 获取锁成功，设置当前节点为 head 节点，并且向后传播</span>                    <span class="token function">setHeadAndPropagate</span><span class="token punctuation">(</span>node<span class="token punctuation">,</span> r<span class="token punctuation">)</span><span class="token punctuation">;</span>                    p<span class="token punctuation">.</span>next <span class="token operator">=</span> null<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// help GC</span>                    failed <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>                    <span class="token keyword">return</span><span class="token punctuation">;</span>                <span class="token punctuation">}</span>            <span class="token punctuation">}</span>            <span class="token comment" spellcheck="true">// 阻塞在这里</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">shouldParkAfterFailedAcquire</span><span class="token punctuation">(</span>p<span class="token punctuation">,</span> node<span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> <span class="token function">parkAndCheckInterrupt</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>                <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">InterruptedException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 阻塞线程被中断后抛出异常，进入取消节点的逻辑</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>failed<span class="token punctuation">)</span>            <span class="token function">cancelAcquire</span><span class="token punctuation">(</span>node<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>获取共享锁成功，进入唤醒阻塞队列中与头节点相连的 SHARED 模式的节点：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">setHeadAndPropagate</span><span class="token punctuation">(</span>Node node<span class="token punctuation">,</span> <span class="token keyword">int</span> propagate<span class="token punctuation">)</span> <span class="token punctuation">{</span>    Node h <span class="token operator">=</span> head<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 将当前节点设置为新的 head 节点，前驱节点和持有线程置为 null</span>    <span class="token function">setHead</span><span class="token punctuation">(</span>node<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// propagate = 1，条件一成立</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>propagate <span class="token operator">></span> <span class="token number">0</span> <span class="token operator">||</span> h <span class="token operator">==</span> null <span class="token operator">||</span> h<span class="token punctuation">.</span>waitStatus <span class="token operator">&lt;</span> <span class="token number">0</span> <span class="token operator">||</span> <span class="token punctuation">(</span>h <span class="token operator">=</span> head<span class="token punctuation">)</span> <span class="token operator">==</span> null <span class="token operator">||</span> h<span class="token punctuation">.</span>waitStatus <span class="token operator">&lt;</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 获取当前节点的后继节点</span>        Node s <span class="token operator">=</span> node<span class="token punctuation">.</span>next<span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 当前节点是尾节点时 next 为 null，或者后继节点是 SHARED 共享模式</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>s <span class="token operator">==</span> null <span class="token operator">||</span> s<span class="token punctuation">.</span><span class="token function">isShared</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>            <span class="token comment" spellcheck="true">// 唤醒所有的等待共享锁的节点</span>            <span class="token function">doReleaseShared</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li></ul><p>计数减一：</p><ul><li><p>线程进入 countDown() 完成计数器减一（释放锁）的操作</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">countDown</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    sync<span class="token punctuation">.</span><span class="token function">releaseShared</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token keyword">boolean</span> <span class="token function">releaseShared</span><span class="token punctuation">(</span><span class="token keyword">int</span> arg<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 尝试释放共享锁</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">tryReleaseShared</span><span class="token punctuation">(</span>arg<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 释放锁成功开始唤醒阻塞节点</span>        <span class="token function">doReleaseShared</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>更新 state 值，每调用一次，state 值减一，当 state -1 正好为 0 时，返回 true</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">protected</span> <span class="token keyword">boolean</span> <span class="token function">tryReleaseShared</span><span class="token punctuation">(</span><span class="token keyword">int</span> releases<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token punctuation">;</span><span class="token punctuation">;</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">int</span> c <span class="token operator">=</span> <span class="token function">getState</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 条件成立说明前面【已经有线程触发唤醒操作】了，这里返回 false</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>c <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span>            <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 计数器减一</span>        <span class="token keyword">int</span> nextc <span class="token operator">=</span> c<span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">compareAndSetState</span><span class="token punctuation">(</span>c<span class="token punctuation">,</span> nextc<span class="token punctuation">)</span><span class="token punctuation">)</span>            <span class="token comment" spellcheck="true">// 计数器为 0 时返回 true</span>            <span class="token keyword">return</span> nextc <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>state &#x3D; 0 时，当前线程需要执行<strong>唤醒阻塞节点的任务</strong></p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">doReleaseShared</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token punctuation">;</span><span class="token punctuation">;</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        Node h <span class="token operator">=</span> head<span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 判断队列是否是空队列</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>h <span class="token operator">!=</span> null <span class="token operator">&amp;&amp;</span> h <span class="token operator">!=</span> tail<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token keyword">int</span> ws <span class="token operator">=</span> h<span class="token punctuation">.</span>waitStatus<span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">// 头节点的状态为 signal，说明后继节点没有被唤醒过</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span>ws <span class="token operator">==</span> Node<span class="token punctuation">.</span>SIGNAL<span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token comment" spellcheck="true">// cas 设置头节点的状态为 0，设置失败继续自旋</span>                <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">compareAndSetWaitStatus</span><span class="token punctuation">(</span>h<span class="token punctuation">,</span> Node<span class="token punctuation">.</span>SIGNAL<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">)</span>                    <span class="token keyword">continue</span><span class="token punctuation">;</span>                <span class="token comment" spellcheck="true">// 唤醒后继节点</span>                <span class="token function">unparkSuccessor</span><span class="token punctuation">(</span>h<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>            <span class="token comment" spellcheck="true">// 如果有其他线程已经设置了头节点的状态，重新设置为 PROPAGATE 传播属性</span>            <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>ws <span class="token operator">==</span> <span class="token number">0</span> <span class="token operator">&amp;&amp;</span> <span class="token operator">!</span><span class="token function">compareAndSetWaitStatus</span><span class="token punctuation">(</span>h<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> Node<span class="token punctuation">.</span>PROPAGATE<span class="token punctuation">)</span><span class="token punctuation">)</span>                <span class="token keyword">continue</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 条件不成立说明被唤醒的节点非常积极，直接将自己设置为了新的head，</span>        <span class="token comment" spellcheck="true">// 此时唤醒它的节点（前驱）执行 h == head 不成立，所以不会跳出循环，会继续唤醒新的 head 节点的后继节点</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>h <span class="token operator">==</span> head<span class="token punctuation">)</span>            <span class="token keyword">break</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li></ul><h2 id="❸CyclicBarrier"><a href="#❸CyclicBarrier" class="headerlink" title="❸CyclicBarrier"></a>❸CyclicBarrier</h2><h3 id="基本使用-8"><a href="#基本使用-8" class="headerlink" title="基本使用"></a>基本使用</h3><p>CyclicBarrier：循环屏障，用来进行线程协作，等待线程满足某个计数，才能触发自己执行</p><p>常用方法：</p><ul><li><code>public CyclicBarrier(int parties, Runnable barrierAction)</code>：用于在线程到达屏障 parties 时，执行 barrierAction<ul><li>parties：代表多少个线程到达屏障开始触发线程任务</li><li>barrierAction：线程任务</li></ul></li><li><code>public int await()</code>：线程调用 await 方法通知 CyclicBarrier 本线程已经到达屏障</li></ul><p>与 CountDownLatch 的区别：CyclicBarrier 是可以重用的</p><p>应用：可以实现多线程中，某个任务在等待其他线程执行完毕以后触发</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>    ExecutorService service <span class="token operator">=</span> Executors<span class="token punctuation">.</span><span class="token function">newFixedThreadPool</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    CyclicBarrier barrier <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">CyclicBarrier</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"task1 task2 finish..."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> <span class="token number">3</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// 循环重用</span>        service<span class="token punctuation">.</span><span class="token function">submit</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span>            System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"task1 begin..."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">try</span> <span class="token punctuation">{</span>                Thread<span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">1000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                barrier<span class="token punctuation">.</span><span class="token function">await</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 2 - 1 = 1</span>            <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">InterruptedException</span> <span class="token operator">|</span> BrokenBarrierException e<span class="token punctuation">)</span> <span class="token punctuation">{</span>                e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        service<span class="token punctuation">.</span><span class="token function">submit</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span>            System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"task2 begin..."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">try</span> <span class="token punctuation">{</span>                Thread<span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">2000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                barrier<span class="token punctuation">.</span><span class="token function">await</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 1 - 1 = 0</span>            <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">InterruptedException</span> <span class="token operator">|</span> BrokenBarrierException e<span class="token punctuation">)</span> <span class="token punctuation">{</span>                e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    service<span class="token punctuation">.</span><span class="token function">shutdown</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="实现原理-7"><a href="#实现原理-7" class="headerlink" title="实现原理"></a>实现原理</h3><h4 id="成员属性-4"><a href="#成员属性-4" class="headerlink" title="成员属性"></a>成员属性</h4><ul><li><p>全局锁：利用可重入锁实现的工具类</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// barrier 实现是依赖于Condition条件队列，condition 条件队列必须依赖lock才能使用</span><span class="token keyword">private</span> <span class="token keyword">final</span> ReentrantLock lock <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ReentrantLock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 线程挂起实现使用的 condition 队列，当前代所有线程到位，这个条件队列内的线程才会被唤醒</span><span class="token keyword">private</span> <span class="token keyword">final</span> Condition trip <span class="token operator">=</span> lock<span class="token punctuation">.</span><span class="token function">newCondition</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>线程数量：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">final</span> <span class="token keyword">int</span> parties<span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 代表多少个线程到达屏障开始触发线程任务</span><span class="token keyword">private</span> <span class="token keyword">int</span> count<span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 表示当前“代”还有多少个线程未到位，初始值为 parties</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre></li><li><p>当前代中最后一个线程到位后要执行的事件：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">final</span> Runnable barrierCommand<span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre></li><li><p>代：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// 表示 barrier 对象当前 代</span><span class="token keyword">private</span> Generation generation <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Generation</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">Generation</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 表示当前“代”是否被打破，如果被打破再来到这一代的线程 就会直接抛出 BrokenException 异常</span>    <span class="token comment" spellcheck="true">// 且在这一代挂起的线程都会被唤醒，然后抛出 BrokerException 异常。</span>    <span class="token keyword">boolean</span> broken <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>构造方法：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token function">CyclicBarrie</span><span class="token punctuation">(</span><span class="token keyword">int</span> parties<span class="token punctuation">,</span> Runnable barrierAction<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 因为小于等于 0 的 barrier 没有任何意义</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>parties <span class="token operator">&lt;=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">IllegalArgumentException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>parties <span class="token operator">=</span> parties<span class="token punctuation">;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>count <span class="token operator">=</span> parties<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 可以为 null</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>barrierCommand <span class="token operator">=</span> barrierAction<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li></ul><img src="https://img.jwt1399.top/img/202301061955546.png" style="zoom: 80%;" /><h4 id="成员方法-4"><a href="#成员方法-4" class="headerlink" title="成员方法"></a>成员方法</h4><ul><li><p>await()：阻塞等待所有线程到位</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">await</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">throws</span> InterruptedException<span class="token punctuation">,</span> BrokenBarrierException <span class="token punctuation">{</span>    <span class="token keyword">try</span> <span class="token punctuation">{</span>        <span class="token keyword">return</span> <span class="token function">dowait</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">,</span> 0L<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">TimeoutException</span> toe<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span>toe<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// cannot happen</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// timed：表示当前调用await方法的线程是否指定了超时时长，如果 true 表示线程是响应超时的</span><span class="token comment" spellcheck="true">// nanos：线程等待超时时长，单位是纳秒</span><span class="token keyword">private</span> <span class="token keyword">int</span> <span class="token function">dowait</span><span class="token punctuation">(</span><span class="token keyword">boolean</span> timed<span class="token punctuation">,</span> <span class="token keyword">long</span> nanos<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">final</span> ReentrantLock lock <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>lock<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 加锁</span>    lock<span class="token punctuation">.</span><span class="token function">lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">try</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 获取当前代</span>        <span class="token keyword">final</span> Generation g <span class="token operator">=</span> generation<span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 【如果当前代是已经被打破状态，则当前调用await方法的线程，直接抛出Broken异常】</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>g<span class="token punctuation">.</span>broken<span class="token punctuation">)</span>            <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">BrokenBarrierException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 如果当前线程被中断了，则打破当前代，然后当前线程抛出中断异常</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>Thread<span class="token punctuation">.</span><span class="token function">interrupted</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 设置当前代的状态为 broken 状态，唤醒在 trip 条件队列内的线程</span>            <span class="token function">breakBarrier</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">InterruptedException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 逻辑到这说明，当前线程中断状态是 false， 当前代的 broken 为 false（未打破状态）</span>                <span class="token comment" spellcheck="true">// 假设 parties 给的是 5，那么index对应的值为 4,3,2,1,0</span>        <span class="token keyword">int</span> index <span class="token operator">=</span> <span class="token operator">--</span>count<span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 条件成立说明当前线程是最后一个到达 barrier 的线程，【需要开启新代，唤醒阻塞线程】</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>index <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 栅栏任务启动标记</span>            <span class="token keyword">boolean</span> ranAction <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>            <span class="token keyword">try</span> <span class="token punctuation">{</span>                <span class="token keyword">final</span> Runnable command <span class="token operator">=</span> barrierCommand<span class="token punctuation">;</span>                <span class="token keyword">if</span> <span class="token punctuation">(</span>command <span class="token operator">!=</span> null<span class="token punctuation">)</span>                    <span class="token comment" spellcheck="true">// 启动触发的任务</span>                    command<span class="token punctuation">.</span><span class="token function">run</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token comment" spellcheck="true">// run()未抛出异常的话，启动标记设置为 true</span>                ranAction <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>                <span class="token comment" spellcheck="true">// 开启新的一代，这里会【唤醒所有的阻塞队列】</span>                <span class="token function">nextGeneration</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token comment" spellcheck="true">// 返回 0 因为当前线程是此代最后一个到达的线程，index == 0</span>                <span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>                <span class="token comment" spellcheck="true">// 如果 command.run() 执行抛出异常的话，会进入到这里</span>                <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>ranAction<span class="token punctuation">)</span>                    <span class="token function">breakBarrier</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 自旋，一直到条件满足、当前代被打破、线程被中断，等待超时</span>        <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token punctuation">;</span><span class="token punctuation">;</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token keyword">try</span> <span class="token punctuation">{</span>                <span class="token comment" spellcheck="true">// 根据是否需要超时等待选择阻塞方法</span>                <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>timed<span class="token punctuation">)</span>                    <span class="token comment" spellcheck="true">// 当前线程释放掉 lock，【进入到 trip 条件队列的尾部挂起自己】，等待被唤醒</span>                    trip<span class="token punctuation">.</span><span class="token function">await</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>nanos <span class="token operator">></span> 0L<span class="token punctuation">)</span>                    nanos <span class="token operator">=</span> trip<span class="token punctuation">.</span><span class="token function">awaitNanos</span><span class="token punctuation">(</span>nanos<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">InterruptedException</span> ie<span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token comment" spellcheck="true">// 被中断后来到这里的逻辑</span>                                <span class="token comment" spellcheck="true">// 当前代没有变化并且没有被打破</span>                <span class="token keyword">if</span> <span class="token punctuation">(</span>g <span class="token operator">==</span> generation <span class="token operator">&amp;&amp;</span> <span class="token operator">!</span>g<span class="token punctuation">.</span>broken<span class="token punctuation">)</span> <span class="token punctuation">{</span>                    <span class="token comment" spellcheck="true">// 打破屏障</span>                    <span class="token function">breakBarrier</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                    <span class="token comment" spellcheck="true">// node 节点在【条件队列】内收到中断信号时 会抛出中断异常</span>                    <span class="token keyword">throw</span> ie<span class="token punctuation">;</span>                <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>                    <span class="token comment" spellcheck="true">// 等待过程中代变化了，完成一次自我打断</span>                    Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">interrupt</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token punctuation">}</span>            <span class="token punctuation">}</span>            <span class="token comment" spellcheck="true">// 唤醒后的线程，【判断当前代已经被打破，线程唤醒后依次抛出 BrokenBarrier 异常】</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span>g<span class="token punctuation">.</span>broken<span class="token punctuation">)</span>                <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">BrokenBarrierException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">// 当前线程挂起期间，最后一个线程到位了，然后触发了开启新的一代的逻辑</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span>g <span class="token operator">!=</span> generation<span class="token punctuation">)</span>                <span class="token keyword">return</span> index<span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">// 当前线程 trip 中等待超时，然后主动转移到阻塞队列</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span>timed <span class="token operator">&amp;&amp;</span> nanos <span class="token operator">&lt;=</span> 0L<span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token function">breakBarrier</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token comment" spellcheck="true">// 抛出超时异常</span>                <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">TimeoutException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 解锁</span>        lock<span class="token punctuation">.</span><span class="token function">unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>breakBarrier()：打破 Barrier 屏障</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">breakBarrier</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 将代中的 broken 设置为 true，表示这一代是被打破了，再来到这一代的线程，直接抛出异常</span>    generation<span class="token punctuation">.</span>broken <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 重置 count 为 parties</span>    count <span class="token operator">=</span> parties<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 将在trip条件队列内挂起的线程全部唤醒，唤醒后的线程会检查当前是否是打破的，然后抛出异常</span>    trip<span class="token punctuation">.</span><span class="token function">signalAll</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>nextGeneration()：开启新的下一代 </p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">nextGeneration</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 将在 trip 条件队列内挂起的线程全部唤醒</span>    trip<span class="token punctuation">.</span><span class="token function">signalAll</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 重置 count 为 parties</span>    count <span class="token operator">=</span> parties<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 开启新的一代，使用一个新的generation对象，表示新的一代，新的一代和上一代【没有任何关系】</span>    generation <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Generation</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li></ul><h2 id="❹Exchanger"><a href="#❹Exchanger" class="headerlink" title="❹Exchanger"></a>❹Exchanger</h2><p>Exchanger：交换器，是一个用于线程间协作的工具类，用于进行线程间的数据交换</p><p>工作流程：两个线程通过 exchange 方法交换数据，如果第一个线程先执行 exchange() 方法，它会一直等待第二个线程也执行 exchange 方法，当两个线程都到达同步点时，这两个线程就可以交换数据</p><p>常用方法：</p><ul><li><code>public Exchanger()</code>：创建一个新的交换器</li><li><code>public V exchange(V x)</code>：等待另一个线程到达此交换点</li><li><code>public V exchange(V x, long timeout, TimeUnit unit)</code>：等待一定的时间</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ExchangerDemo</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 创建交换对象（信使）</span>        Exchanger<span class="token operator">&lt;</span>String<span class="token operator">></span> exchanger <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Exchanger</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">new</span> <span class="token class-name">ThreadA</span><span class="token punctuation">(</span>exchanger<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">new</span> <span class="token class-name">ThreadB</span><span class="token punctuation">(</span>exchanger<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token keyword">class</span> <span class="token class-name">ThreadA</span> <span class="token keyword">extends</span> <span class="token class-name">Thread</span><span class="token punctuation">{</span>    <span class="token keyword">private</span> Exchanger<span class="token operator">&lt;</span>String<span class="token operator">></span> <span class="token function">exchanger</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">public</span> <span class="token function">ThreadA</span><span class="token punctuation">(</span>Exchanger<span class="token operator">&lt;</span>String<span class="token operator">></span> exchanger<span class="token punctuation">)</span><span class="token punctuation">{</span>        <span class="token keyword">this</span><span class="token punctuation">.</span>exchanger <span class="token operator">=</span> exchanger<span class="token punctuation">;</span>    <span class="token punctuation">}</span>        <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">run</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">try</span><span class="token punctuation">{</span>            <span class="token function">sout</span><span class="token punctuation">(</span><span class="token string">"线程A，做好了礼物A，等待线程B送来的礼物B"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">//如果等待了5s还没有交换就死亡（抛出异常）！</span>            String s <span class="token operator">=</span> exchanger<span class="token punctuation">.</span><span class="token function">exchange</span><span class="token punctuation">(</span><span class="token string">"礼物A"</span><span class="token punctuation">,</span><span class="token number">5</span><span class="token punctuation">,</span>TimeUnit<span class="token punctuation">.</span>SECONDS<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token function">sout</span><span class="token punctuation">(</span><span class="token string">"线程A收到线程B的礼物："</span> <span class="token operator">+</span> s<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Exception</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span>            System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"线程A等待了5s，没有收到礼物,最终就执行结束了!"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token keyword">class</span> <span class="token class-name">ThreadB</span> <span class="token keyword">extends</span> <span class="token class-name">Thread</span><span class="token punctuation">{</span>    <span class="token keyword">private</span> Exchanger<span class="token operator">&lt;</span>String<span class="token operator">></span> exchanger<span class="token punctuation">;</span>        <span class="token keyword">public</span> <span class="token function">ThreadB</span><span class="token punctuation">(</span>Exchanger<span class="token operator">&lt;</span>String<span class="token operator">></span> exchanger<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">this</span><span class="token punctuation">.</span>exchanger <span class="token operator">=</span> exchanger<span class="token punctuation">;</span>    <span class="token punctuation">}</span>        <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">run</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">try</span> <span class="token punctuation">{</span>            <span class="token function">sout</span><span class="token punctuation">(</span><span class="token string">"线程B,做好了礼物B,等待线程A送来的礼物A....."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">// 开始交换礼物。参数是送给其他线程的礼物!</span>            <span class="token function">sout</span><span class="token punctuation">(</span><span class="token string">"线程B收到线程A的礼物："</span> <span class="token operator">+</span> exchanger<span class="token punctuation">.</span><span class="token function">exchange</span><span class="token punctuation">(</span><span class="token string">"礼物B"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Exception</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span>            e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h1 id="⑧并发容器"><a href="#⑧并发容器" class="headerlink" title="⑧并发容器"></a>⑧并发容器</h1><h2 id="❶ConcurrentHashMap"><a href="#❶ConcurrentHashMap" class="headerlink" title="❶ConcurrentHashMap"></a>❶ConcurrentHashMap</h2><h3 id="集合对比"><a href="#集合对比" class="headerlink" title="集合对比"></a>集合对比</h3><p>三种集合：</p><ul><li>HashMap 是线程不安全的，性能好</li><li>Hashtable 线程安全基于 synchronized，综合性能差，已经被淘汰</li><li>ConcurrentHashMap 保证了线程安全，综合性能较好</li></ul><p>集合对比：</p><ol><li>Hashtable 继承 Dictionary 类，HashMap、ConcurrentHashMap 继承 AbstractMap，均实现 Map 接口</li><li>Hashtable 底层是数组 + 链表，JDK8 以后 HashMap 和 ConcurrentHashMap 底层是数组 + 链表 + 红黑树</li><li>HashMap 线程不安全，Hashtable 线程安全，Hashtable 的方法都加了 synchronized 关来确保线程同步</li><li>ConcurrentHashMap、Hashtable <strong>不允许 null 值</strong>，HashMap 允许 null 值</li><li>ConcurrentHashMap、HashMap 的初始容量为 16，Hashtable 初始容量为11，填充因子默认都是 0.75，两种 Map 扩容是当前容量翻倍：capacity * 2，Hashtable 扩容时是容量翻倍 + 1：capacity*2 + 1</li></ol><p><img src="https://img.jwt1399.top/img/202301062156037.png" alt="ConcurrentHashMap数据结构"></p><h3 id="工作步骤："><a href="#工作步骤：" class="headerlink" title="工作步骤："></a>工作步骤：</h3><ol><li><p>初始化，使用 cas 来保证并发安全，懒惰初始化 table</p></li><li><p>树化，当 table.length &lt; 64 时，先尝试扩容，超过 64 时，并且 链表节点数 &gt; 8 时，会将<strong>链表树化</strong>，树化过程会用 synchronized 锁住链表头</p><p>说明：锁住某个槽位的对象头，是一种很好的<strong>细粒度的加锁</strong>方式，类似 MySQL 中的行锁</p></li><li><p>put，如果该 节点 尚未创建，只需要使用 cas 创建 节点；如果已经有了，锁住链表头进行后续 put 操作，元素添加至 节点 的尾部</p></li><li><p>get，无锁操作仅需要保证可见性，扩容过程中 get 操作拿到的是 ForwardingNode 会让 get 操作在新 table 进行搜索</p></li><li><p>扩容，扩容时以 bin节点 为单位进行，需要对 bin节点 进行 synchronized，但这时其它竞争线程也不是无事可做，它们会帮助把其它 bin节点 进行扩容</p></li><li><p>size，元素个数保存在 baseCount 中，并发时的个数变动保存在 CounterCell[] 当中，最后统计数量时累加</p></li></ol><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">//需求：多个线程同时往HashMap容器中存入数据会出现安全问题</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ConcurrentHashMapDemo</span><span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> Map<span class="token operator">&lt;</span>String<span class="token punctuation">,</span>String<span class="token operator">></span> map <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ConcurrentHashMap</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span><span class="token punctuation">{</span>        <span class="token keyword">new</span> <span class="token class-name">AddMapDataThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">new</span> <span class="token class-name">AddMapDataThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                Thread<span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">1000</span> <span class="token operator">*</span> <span class="token number">5</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//休息5秒，确保两个线程执行完毕</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"Map大小："</span> <span class="token operator">+</span> map<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//20万</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">AddMapDataThread</span> <span class="token keyword">extends</span> <span class="token class-name">Thread</span><span class="token punctuation">{</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">run</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span> <span class="token punctuation">;</span> i <span class="token operator">&lt;</span> <span class="token number">1000000</span> <span class="token punctuation">;</span> i<span class="token operator">++</span> <span class="token punctuation">)</span><span class="token punctuation">{</span>            ConcurrentHashMapDemo<span class="token punctuation">.</span>map<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token string">"键："</span><span class="token operator">+</span>i <span class="token punctuation">,</span> <span class="token string">"值"</span><span class="token operator">+</span>i<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="并发死链"><a href="#并发死链" class="headerlink" title="并发死链"></a><strong>并发死链</strong></h3><p>JDK1.7 的 HashMap 采用的头插法（拉链法）进行节点的添加，HashMap 的扩容长度为原来的 2 倍</p><p>resize() 中节点（Entry）转移的源代码：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">void</span> <span class="token function">transfer</span><span class="token punctuation">(</span>Entry<span class="token punctuation">[</span><span class="token punctuation">]</span> newTable<span class="token punctuation">,</span> <span class="token keyword">boolean</span> rehash<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">int</span> newCapacity <span class="token operator">=</span> newTable<span class="token punctuation">.</span>length<span class="token punctuation">;</span><span class="token comment" spellcheck="true">//得到新数组的长度   </span>    <span class="token comment" spellcheck="true">// 遍历整个数组对应下标下的链表，e代表一个节点</span>    <span class="token keyword">for</span> <span class="token punctuation">(</span>Entry<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> e <span class="token operator">:</span> table<span class="token punctuation">)</span> <span class="token punctuation">{</span>           <span class="token comment" spellcheck="true">// 当e == null时，则该链表遍历完了，继续遍历下一数组下标的链表 </span>        <span class="token keyword">while</span><span class="token punctuation">(</span>null <span class="token operator">!=</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span>             <span class="token comment" spellcheck="true">// 先把e节点的下一节点存起来</span>            Entry<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> next <span class="token operator">=</span> e<span class="token punctuation">.</span>next<span class="token punctuation">;</span>             <span class="token keyword">if</span> <span class="token punctuation">(</span>rehash<span class="token punctuation">)</span> <span class="token punctuation">{</span>              <span class="token comment" spellcheck="true">//得到新的hash值</span>                e<span class="token punctuation">.</span>hash <span class="token operator">=</span> null <span class="token operator">==</span> e<span class="token punctuation">.</span>key <span class="token operator">?</span> <span class="token number">0</span> <span class="token operator">:</span> <span class="token function">hash</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>key<span class="token punctuation">)</span><span class="token punctuation">;</span>              <span class="token punctuation">}</span>            <span class="token comment" spellcheck="true">// 在新数组下得到新的数组下标</span>            <span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token function">indexFor</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>hash<span class="token punctuation">,</span> newCapacity<span class="token punctuation">)</span><span class="token punctuation">;</span>               <span class="token comment" spellcheck="true">// 将e的next指针指向新数组下标的位置</span>            e<span class="token punctuation">.</span>next <span class="token operator">=</span> newTable<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">;</span>               <span class="token comment" spellcheck="true">// 将该数组下标的节点变为e节点</span>            newTable<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> e<span class="token punctuation">;</span>             <span class="token comment" spellcheck="true">// 遍历链表的下一节点</span>            e <span class="token operator">=</span> next<span class="token punctuation">;</span>                                           <span class="token punctuation">}</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>JDK 7 HashMap之所以在并发下的扩容造成死循环，是因为，多个线程并发进行时，因为一个线程先期完成了扩容，将原的链表重新散列到自己的表中，并且链表变成了倒序（头插法），后一个线程再扩容时，又进行自己的散列，再次将倒序链表变为正序链表。于是形成了一个环形链表，当表中不存在的元素时，造成死循环。</p><p>JDK 8 虽然将扩容算法做了调整，改用了尾插法，但仍不意味着能够在多线程环境下能够安全扩容，还会出现其它问题（如扩容丢数据）</p><h3 id="成员属性-5"><a href="#成员属性-5" class="headerlink" title="成员属性"></a>成员属性</h3><h4 id="变量"><a href="#变量" class="headerlink" title="变量"></a>变量</h4><ul><li><p>存储数组：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">transient</span> <span class="token keyword">volatile</span> Node<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span> table<span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre></li><li><p>散列表的长度：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> MAXIMUM_CAPACITY <span class="token operator">=</span> <span class="token number">1</span> <span class="token operator">&lt;&lt;</span> <span class="token number">30</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 最大长度</span><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> DEFAULT_CAPACITY <span class="token operator">=</span> <span class="token number">16</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 默认长度</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre></li><li><p>并发级别，JDK7 遗留下来，1.8 中不代表并发级别：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> DEFAULT_CONCURRENCY_LEVEL <span class="token operator">=</span> <span class="token number">16</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre></li><li><p>负载因子，JDK1.8 的 ConcurrentHashMap 中是固定值：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">float</span> LOAD_FACTOR <span class="token operator">=</span> <span class="token number">0.75f</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre></li><li><p>阈值：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> TREEIFY_THRESHOLD <span class="token operator">=</span> <span class="token number">8</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 链表树化的阈值</span><span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> UNTREEIFY_THRESHOLD <span class="token operator">=</span> <span class="token number">6</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 红黑树转化为链表的阈值</span><span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> MIN_TREEIFY_CAPACITY <span class="token operator">=</span> <span class="token number">64</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 当数组长度达到64且某个桶位中的链表长度超过8，才会真正树化</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre></li><li><p>扩容相关：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> MIN_TRANSFER_STRIDE <span class="token operator">=</span> <span class="token number">16</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 线程迁移数据【最小步长】，控制线程迁移任务的最小区间</span><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">int</span> RESIZE_STAMP_BITS <span class="token operator">=</span> <span class="token number">16</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 用来计算扩容时生成的【标识戳】</span><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> MAX_RESIZERS <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token number">1</span> <span class="token operator">&lt;&lt;</span> <span class="token punctuation">(</span><span class="token number">32</span> <span class="token operator">-</span> RESIZE_STAMP_BITS<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 65535-1并发扩容最多线程数</span><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> RESIZE_STAMP_SHIFT <span class="token operator">=</span> <span class="token number">32</span> <span class="token operator">-</span> RESIZE_STAMP_BITS<span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 扩容时使用</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>节点哈希值：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> MOVED     <span class="token operator">=</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 表示当前节点是 FWD 节点</span><span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> TREEBIN   <span class="token operator">=</span> <span class="token operator">-</span><span class="token number">2</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 表示当前节点已经树化，且当前节点为 TreeBin 对象</span><span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> RESERVED  <span class="token operator">=</span> <span class="token operator">-</span><span class="token number">3</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 表示节点时临时节点</span><span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> HASH_BITS <span class="token operator">=</span> <span class="token number">0x7fffffff</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 正常节点的哈希值的可用的位数</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>扩容过程：volatile 修饰保证多线程的可见性</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// 扩容过程中，会将扩容中的新 table 赋值给 nextTable 保持引用，扩容结束之后，这里会被设置为 null</span><span class="token keyword">private</span> <span class="token keyword">transient</span> <span class="token keyword">volatile</span> Node<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span> nextTable<span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 记录扩容进度，所有线程都要从 0 - transferIndex 中分配区间任务，简单说就是老表转移到哪了，索引从高到低转移</span><span class="token keyword">private</span> <span class="token keyword">transient</span> <span class="token keyword">volatile</span> <span class="token keyword">int</span> transferIndex<span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>累加统计：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// LongAdder 中的 baseCount 未发生竞争时或者当前LongAdder处于加锁状态时，增量累到到 baseCount 中</span><span class="token keyword">private</span> <span class="token keyword">transient</span> <span class="token keyword">volatile</span> <span class="token keyword">long</span> baseCount<span class="token punctuation">;</span><span class="token comment" spellcheck="true">// LongAdder 中的 cellsBuzy，0 表示当前 LongAdder 对象无锁状态，1 表示当前 LongAdder 对象加锁状态</span><span class="token keyword">private</span> <span class="token keyword">transient</span> <span class="token keyword">volatile</span> <span class="token keyword">int</span> cellsBusy<span class="token punctuation">;</span><span class="token comment" spellcheck="true">// LongAdder 中的 cells 数组，</span><span class="token keyword">private</span> <span class="token keyword">transient</span> <span class="token keyword">volatile</span> CounterCell<span class="token punctuation">[</span><span class="token punctuation">]</span> counterCells<span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>控制变量：</p><p><strong>sizeCtl</strong> &lt; 0：</p><ul><li><p>-1 表示当前 table 正在初始化（有线程在创建 table 数组），当前线程需要自旋等待</p></li><li><p>其他负数表示当前 map 的 table 数组正在进行扩容，高 16 位表示扩容的标识戳；低 16 位表示 (1 + nThread) 当前参与并发扩容的线程数量 + 1</p></li></ul><p>sizeCtl &#x3D; 0，表示创建 table 数组时使用 DEFAULT_CAPACITY 为数组大小</p><p>sizeCtl &gt; 0：</p><ul><li>如果 table 未初始化，表示初始化大小</li><li>如果 table 已经初始化，表示下次扩容时的触发条件（阈值，元素个数，不是数组的长度）</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">transient</span> <span class="token keyword">volatile</span> <span class="token keyword">int</span> sizeCtl<span class="token punctuation">;</span><span class="token comment" spellcheck="true">// volatile 保持可见性</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre></li></ul><h4 id="内部类"><a href="#内部类" class="headerlink" title="内部类"></a>内部类</h4><ul><li><p>Node 节点：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">Node</span><span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> <span class="token keyword">implements</span> <span class="token class-name">Entry</span><span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 节点哈希值</span>    <span class="token keyword">final</span> <span class="token keyword">int</span> hash<span class="token punctuation">;</span>    <span class="token keyword">final</span> K key<span class="token punctuation">;</span>    <span class="token keyword">volatile</span> V val<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 单向链表</span>    <span class="token keyword">volatile</span> Node<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> next<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>TreeBin 节点：</p><pre class="line-numbers language-java"><code class="language-java"> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">class</span> <span class="token class-name">TreeBin</span><span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> <span class="token keyword">extends</span> <span class="token class-name">Node</span><span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> <span class="token punctuation">{</span>     <span class="token comment" spellcheck="true">// 红黑树根节点</span>     TreeNode<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> root<span class="token punctuation">;</span>     <span class="token comment" spellcheck="true">// 链表的头节点</span>     <span class="token keyword">volatile</span> TreeNode<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> first<span class="token punctuation">;</span>     <span class="token comment" spellcheck="true">// 等待者线程</span>     <span class="token keyword">volatile</span> Thread waiter<span class="token punctuation">;</span>     <span class="token keyword">volatile</span> <span class="token keyword">int</span> lockState<span class="token punctuation">;</span>     <span class="token comment" spellcheck="true">// 写锁状态 写锁是独占状态，以散列表来看，真正进入到 TreeBin 中的写线程同一时刻只有一个线程</span>     <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> WRITER <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>     <span class="token comment" spellcheck="true">// 等待者状态（写线程在等待），当 TreeBin 中有读线程目前正在读取数据时，写线程无法修改数据</span>     <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> WAITER <span class="token operator">=</span> <span class="token number">2</span><span class="token punctuation">;</span>     <span class="token comment" spellcheck="true">// 读锁状态是共享，同一时刻可以有多个线程 同时进入到 TreeBi 对象中获取数据，每一个线程都给 lockState + 4</span>     <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> READER <span class="token operator">=</span> <span class="token number">4</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>TreeNode 节点：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">class</span> <span class="token class-name">TreeNode</span><span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> <span class="token keyword">extends</span> <span class="token class-name">Node</span><span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> <span class="token punctuation">{</span>    TreeNode<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> parent<span class="token punctuation">;</span>  <span class="token comment" spellcheck="true">// red-black tree links</span>    TreeNode<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> left<span class="token punctuation">;</span>    TreeNode<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> right<span class="token punctuation">;</span>    TreeNode<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> prev<span class="token punctuation">;</span>   <span class="token comment" spellcheck="true">//双向链表</span>    <span class="token keyword">boolean</span> red<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>ForwardingNode 节点：转移节点</p><pre class="line-numbers language-java"><code class="language-java"> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">class</span> <span class="token class-name">ForwardingNode</span><span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> <span class="token keyword">extends</span> <span class="token class-name">Node</span><span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> <span class="token punctuation">{</span>     <span class="token comment" spellcheck="true">// 持有扩容后新的哈希表的引用</span>     <span class="token keyword">final</span> Node<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span> nextTable<span class="token punctuation">;</span>     <span class="token function">ForwardingNode</span><span class="token punctuation">(</span>Node<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span> tab<span class="token punctuation">)</span> <span class="token punctuation">{</span>         <span class="token comment" spellcheck="true">// ForwardingNode 节点的 hash 值设为 -1</span>         <span class="token keyword">super</span><span class="token punctuation">(</span>MOVED<span class="token punctuation">,</span> null<span class="token punctuation">,</span> null<span class="token punctuation">,</span> null<span class="token punctuation">)</span><span class="token punctuation">;</span>         <span class="token keyword">this</span><span class="token punctuation">.</span>nextTable <span class="token operator">=</span> tab<span class="token punctuation">;</span>     <span class="token punctuation">}</span> <span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li></ul><h4 id="代码块"><a href="#代码块" class="headerlink" title="代码块"></a>代码块</h4><ul><li><p>变量：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// 表示sizeCtl属性在 ConcurrentHashMap 中内存偏移地址</span><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">long</span> SIZECTL<span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 表示transferIndex属性在 ConcurrentHashMap 中内存偏移地址</span><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">long</span> TRANSFERINDEX<span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 表示baseCount属性在 ConcurrentHashMap 中内存偏移地址</span><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">long</span> BASECOUNT<span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 表示cellsBusy属性在 ConcurrentHashMap 中内存偏移地址</span><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">long</span> CELLSBUSY<span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 表示cellValue属性在 CounterCell 中内存偏移地址</span><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">long</span> CELLVALUE<span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 表示数组第一个元素的偏移地址</span><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">long</span> ABASE<span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 用位移运算替代乘法</span><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> ASHIFT<span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>赋值方法：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// 表示数组单元所占用空间大小，scale 表示 Node[] 数组中每一个单元所占用空间大小，int 是 4 字节</span><span class="token keyword">int</span> scale <span class="token operator">=</span> U<span class="token punctuation">.</span><span class="token function">arrayIndexScale</span><span class="token punctuation">(</span>ak<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 判断一个数是不是 2 的 n 次幂，比如 8：1000 &amp; 0111 = 0000</span><span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>scale <span class="token operator">&amp;</span> <span class="token punctuation">(</span>scale <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">!=</span> <span class="token number">0</span><span class="token punctuation">)</span>    <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token string">"data type scale not a power of two"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// numberOfLeadingZeros(n)：返回当前数值转换为二进制后，从高位到低位开始统计，看有多少个0连续在一起</span><span class="token comment" spellcheck="true">// 8 → 1000 numberOfLeadingZeros(8) = 28</span><span class="token comment" spellcheck="true">// 4 → 100 numberOfLeadingZeros(4) = 29   int 值就是占4个字节</span>ASHIFT <span class="token operator">=</span> <span class="token number">31</span> <span class="token operator">-</span> Integer<span class="token punctuation">.</span><span class="token function">numberOfLeadingZeros</span><span class="token punctuation">(</span>scale<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// ASHIFT = 31 - 29 = 2 ，int 的大小就是 2 的 2 次方，获取次方数</span><span class="token comment" spellcheck="true">// ABASE + （5 &lt;&lt; ASHIFT） 用位移运算替代了乘法，获取 arr[5] 的值</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li></ul><h4 id="构造方法"><a href="#构造方法" class="headerlink" title="构造方法"></a>构造方法</h4><ul><li><p>无参构造， 散列表结构延迟初始化，默认的数组大小是 16：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token function">ConcurrentHashMap</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre></li><li><p>有参构造：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token function">ConcurrentHashMap</span><span class="token punctuation">(</span><span class="token keyword">int</span> initialCapacity<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 指定容量初始化</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>initialCapacity <span class="token operator">&lt;</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">IllegalArgumentException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">int</span> cap <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>initialCapacity <span class="token operator">>=</span> <span class="token punctuation">(</span>MAXIMUM_CAPACITY <span class="token operator">>>></span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">?</span>               MAXIMUM_CAPACITY <span class="token operator">:</span>               <span class="token comment" spellcheck="true">// 假如传入的参数是 16，16 + 8 + 1 ，最后得到 32</span>               <span class="token comment" spellcheck="true">// 传入 12， 12 + 6 + 1 = 19，最后得到 32，尽可能的大，与 HashMap不一样</span>               <span class="token function">tableSizeFor</span><span class="token punctuation">(</span>initialCapacity <span class="token operator">+</span> <span class="token punctuation">(</span>initialCapacity <span class="token operator">>>></span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// sizeCtl > 0，当目前 table 未初始化时，sizeCtl 表示初始化容量</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>sizeCtl <span class="token operator">=</span> cap<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> <span class="token function">tableSizeFor</span><span class="token punctuation">(</span><span class="token keyword">int</span> c<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">int</span> n <span class="token operator">=</span> c <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">;</span>    n <span class="token operator">|=</span> n <span class="token operator">>>></span> <span class="token number">1</span><span class="token punctuation">;</span>    n <span class="token operator">|=</span> n <span class="token operator">>>></span> <span class="token number">2</span><span class="token punctuation">;</span>    n <span class="token operator">|=</span> n <span class="token operator">>>></span> <span class="token number">4</span><span class="token punctuation">;</span>    n <span class="token operator">|=</span> n <span class="token operator">>>></span> <span class="token number">8</span><span class="token punctuation">;</span>    n <span class="token operator">|=</span> n <span class="token operator">>>></span> <span class="token number">16</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> <span class="token punctuation">(</span>n <span class="token operator">&lt;</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token number">1</span> <span class="token operator">:</span> <span class="token punctuation">(</span>n <span class="token operator">>=</span> MAXIMUM_CAPACITY<span class="token punctuation">)</span> <span class="token operator">?</span> MAXIMUM_CAPACITY <span class="token operator">:</span> n <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>核心思想就是<strong>把最高位是 1 的位以及右边的位全部置 1</strong>，结果加 1 后就是 2 的 n 次幂</p></li><li><p>多个参数构造方法：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token function">ConcurrentHashMap</span><span class="token punctuation">(</span><span class="token keyword">int</span> initialCapacity<span class="token punctuation">,</span> <span class="token keyword">float</span> loadFactor<span class="token punctuation">,</span> <span class="token keyword">int</span> concurrencyLevel<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token punctuation">(</span>loadFactor <span class="token operator">></span> <span class="token number">0.0f</span><span class="token punctuation">)</span> <span class="token operator">||</span> initialCapacity <span class="token operator">&lt;</span> <span class="token number">0</span> <span class="token operator">||</span> concurrencyLevel <span class="token operator">&lt;=</span> <span class="token number">0</span><span class="token punctuation">)</span>        <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">IllegalArgumentException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 初始容量小于并发级别</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>initialCapacity <span class="token operator">&lt;</span> concurrencyLevel<span class="token punctuation">)</span>          <span class="token comment" spellcheck="true">// 把并发级别赋值给初始容量</span>        initialCapacity <span class="token operator">=</span> concurrencyLevel<span class="token punctuation">;</span>     <span class="token comment" spellcheck="true">// loadFactor 默认是 0.75</span>    <span class="token keyword">long</span> size <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token keyword">long</span><span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token number">1.0</span> <span class="token operator">+</span> <span class="token punctuation">(</span><span class="token keyword">long</span><span class="token punctuation">)</span>initialCapacity <span class="token operator">/</span> loadFactor<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">int</span> cap <span class="token operator">=</span> <span class="token punctuation">(</span>size <span class="token operator">>=</span> <span class="token punctuation">(</span><span class="token keyword">long</span><span class="token punctuation">)</span>MAXIMUM_CAPACITY<span class="token punctuation">)</span> <span class="token operator">?</span>        MAXIMUM_CAPACITY <span class="token operator">:</span> <span class="token function">tableSizeFor</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">)</span>size<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// sizeCtl > 0，当目前 table 未初始化时，sizeCtl 表示初始化容量</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>sizeCtl <span class="token operator">=</span> cap<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>集合构造方法：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token function">ConcurrentHashMap</span><span class="token punctuation">(</span>Map<span class="token operator">&lt;</span><span class="token operator">?</span> <span class="token keyword">extends</span> <span class="token class-name">K</span><span class="token punctuation">,</span> <span class="token operator">?</span> <span class="token keyword">extends</span> <span class="token class-name">V</span><span class="token operator">></span> m<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>sizeCtl <span class="token operator">=</span> DEFAULT_CAPACITY<span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 默认16</span>    <span class="token function">putAll</span><span class="token punctuation">(</span>m<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">putAll</span><span class="token punctuation">(</span>Map<span class="token operator">&lt;</span><span class="token operator">?</span> <span class="token keyword">extends</span> <span class="token class-name">K</span><span class="token punctuation">,</span> <span class="token operator">?</span> <span class="token keyword">extends</span> <span class="token class-name">V</span><span class="token operator">></span> m<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 尝试触发扩容</span>    <span class="token function">tryPresize</span><span class="token punctuation">(</span>m<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">for</span> <span class="token punctuation">(</span>Entry<span class="token operator">&lt;</span><span class="token operator">?</span> <span class="token keyword">extends</span> <span class="token class-name">K</span><span class="token punctuation">,</span> <span class="token operator">?</span> <span class="token keyword">extends</span> <span class="token class-name">V</span><span class="token operator">></span> e <span class="token operator">:</span> m<span class="token punctuation">.</span><span class="token function">entrySet</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>        <span class="token function">putVal</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span><span class="token function">getKey</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> e<span class="token punctuation">.</span><span class="token function">getValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">final</span> <span class="token keyword">void</span> <span class="token function">tryPresize</span><span class="token punctuation">(</span><span class="token keyword">int</span> size<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 扩容为大于 2 倍的最小的 2 的 n 次幂</span>    <span class="token keyword">int</span> c <span class="token operator">=</span> <span class="token punctuation">(</span>size <span class="token operator">>=</span> <span class="token punctuation">(</span>MAXIMUM_CAPACITY <span class="token operator">>>></span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">?</span> MAXIMUM_CAPACITY <span class="token operator">:</span>        <span class="token function">tableSizeFor</span><span class="token punctuation">(</span>size <span class="token operator">+</span> <span class="token punctuation">(</span>size <span class="token operator">>>></span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">int</span> sc<span class="token punctuation">;</span>    <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>sc <span class="token operator">=</span> sizeCtl<span class="token punctuation">)</span> <span class="token operator">>=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        Node<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span> tab <span class="token operator">=</span> table<span class="token punctuation">;</span> <span class="token keyword">int</span> n<span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 数组还未初始化，【一般是调用集合构造方法才会成立，put 后调用该方法都是不成立的】</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>tab <span class="token operator">==</span> null <span class="token operator">||</span> <span class="token punctuation">(</span>n <span class="token operator">=</span> tab<span class="token punctuation">.</span>length<span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            n <span class="token operator">=</span> <span class="token punctuation">(</span>sc <span class="token operator">></span> c<span class="token punctuation">)</span> <span class="token operator">?</span> sc <span class="token operator">:</span> c<span class="token punctuation">;</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span>U<span class="token punctuation">.</span><span class="token function">compareAndSwapInt</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> SIZECTL<span class="token punctuation">,</span> sc<span class="token punctuation">,</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token keyword">try</span> <span class="token punctuation">{</span>                    <span class="token keyword">if</span> <span class="token punctuation">(</span>table <span class="token operator">==</span> tab<span class="token punctuation">)</span> <span class="token punctuation">{</span>                        Node<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span> nt <span class="token operator">=</span> <span class="token punctuation">(</span>Node<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token keyword">new</span> <span class="token class-name">Node</span><span class="token operator">&lt;</span><span class="token operator">?</span><span class="token punctuation">,</span><span class="token operator">?</span><span class="token operator">></span><span class="token punctuation">[</span>n<span class="token punctuation">]</span><span class="token punctuation">;</span>                        table <span class="token operator">=</span> nt<span class="token punctuation">;</span>                        sc <span class="token operator">=</span> n <span class="token operator">-</span> <span class="token punctuation">(</span>n <span class="token operator">>>></span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 扩容阈值：n - 1/4 n</span>                    <span class="token punctuation">}</span>                <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>                    sizeCtl <span class="token operator">=</span> sc<span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 扩容阈值赋值给sizeCtl</span>                <span class="token punctuation">}</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 未达到扩容阈值或者数组长度已经大于最大长度</span>        <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>c <span class="token operator">&lt;=</span> sc <span class="token operator">||</span> n <span class="token operator">>=</span> MAXIMUM_CAPACITY<span class="token punctuation">)</span>            <span class="token keyword">break</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 与 addCount 逻辑相同</span>        <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>tab <span class="token operator">==</span> table<span class="token punctuation">)</span> <span class="token punctuation">{</span>                   <span class="token punctuation">}</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li></ul><h3 id="成员方法-5"><a href="#成员方法-5" class="headerlink" title="成员方法"></a>成员方法</h3><h4 id="数据访存"><a href="#数据访存" class="headerlink" title="数据访存"></a>数据访存</h4><ul><li><p>tabAt()：获取数组某个槽位的<strong>头节点</strong>，类似于数组中的直接寻址 arr[i]</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// i 是数组索引</span><span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> Node<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> <span class="token function">tabAt</span><span class="token punctuation">(</span>Node<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span> tab<span class="token punctuation">,</span> <span class="token keyword">int</span> i<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// (i &lt;&lt; ASHIFT) + ABASE == ABASE + i * 4 （一个 int 占 4 个字节），这就相当于寻址，替代了乘法</span>    <span class="token keyword">return</span> <span class="token punctuation">(</span>Node<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">)</span>U<span class="token punctuation">.</span><span class="token function">getObjectVolatile</span><span class="token punctuation">(</span>tab<span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token keyword">long</span><span class="token punctuation">)</span>i <span class="token operator">&lt;&lt;</span> ASHIFT<span class="token punctuation">)</span> <span class="token operator">+</span> ABASE<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>casTabAt()：指定数组索引位置修改原值为指定的值</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> <span class="token keyword">boolean</span> <span class="token function">casTabAt</span><span class="token punctuation">(</span>Node<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span> tab<span class="token punctuation">,</span> <span class="token keyword">int</span> i<span class="token punctuation">,</span> Node<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> c<span class="token punctuation">,</span> Node<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> v<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">return</span> U<span class="token punctuation">.</span><span class="token function">compareAndSwapObject</span><span class="token punctuation">(</span>tab<span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token keyword">long</span><span class="token punctuation">)</span>i <span class="token operator">&lt;&lt;</span> ASHIFT<span class="token punctuation">)</span> <span class="token operator">+</span> ABASE<span class="token punctuation">,</span> c<span class="token punctuation">,</span> v<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre></li><li><p>setTabAt()：指定数组索引位置设置值</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> <span class="token keyword">void</span> <span class="token function">setTabAt</span><span class="token punctuation">(</span>Node<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span> tab<span class="token punctuation">,</span> <span class="token keyword">int</span> i<span class="token punctuation">,</span> Node<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> v<span class="token punctuation">)</span> <span class="token punctuation">{</span>    U<span class="token punctuation">.</span><span class="token function">putObjectVolatile</span><span class="token punctuation">(</span>tab<span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token keyword">long</span><span class="token punctuation">)</span>i <span class="token operator">&lt;&lt;</span> ASHIFT<span class="token punctuation">)</span> <span class="token operator">+</span> ABASE<span class="token punctuation">,</span> v<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre></li></ul><h4 id="添加方法：put"><a href="#添加方法：put" class="headerlink" title="添加方法：put"></a>添加方法：put</h4><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> V <span class="token function">put</span><span class="token punctuation">(</span>K key<span class="token punctuation">,</span> V value<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 第三个参数 onlyIfAbsent 为 false 表示哈希表中存在相同的 key 时【用当前数据覆盖旧数据】</span>    <span class="token keyword">return</span> <span class="token function">putVal</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> value<span class="token punctuation">,</span> <span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><ul><li><p>putVal()</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">final</span> V <span class="token function">putVal</span><span class="token punctuation">(</span>K key<span class="token punctuation">,</span> V value<span class="token punctuation">,</span> <span class="token keyword">boolean</span> onlyIfAbsent<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 【ConcurrentHashMap 不能存放 null 值】</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>key <span class="token operator">==</span> null <span class="token operator">||</span> value <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">NullPointerException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 扰动运算，高低位都参与寻址运算</span>    <span class="token keyword">int</span> hash <span class="token operator">=</span> <span class="token function">spread</span><span class="token punctuation">(</span>key<span class="token punctuation">.</span><span class="token function">hashCode</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 表示当前 k-v 封装成 node 后插入到指定桶位后，在桶位中的所属链表的下标位置</span>    <span class="token keyword">int</span> binCount <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// tab 引用当前 map 的数组 table，开始自旋</span>    <span class="token keyword">for</span> <span class="token punctuation">(</span>Node<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span> tab <span class="token operator">=</span> table<span class="token punctuation">;</span><span class="token punctuation">;</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// f 表示桶位的头节点，n 表示哈希表数组的长度</span>        <span class="token comment" spellcheck="true">// i 表示 key 通过寻址计算后得到的桶位下标，fh 表示桶位头结点的 hash 值</span>        Node<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> f<span class="token punctuation">;</span> <span class="token keyword">int</span> n<span class="token punctuation">,</span> i<span class="token punctuation">,</span> fh<span class="token punctuation">;</span>                <span class="token comment" spellcheck="true">// 【CASE1】：表示当前 map 中的 table 尚未初始化</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>tab <span class="token operator">==</span> null <span class="token operator">||</span> <span class="token punctuation">(</span>n <span class="token operator">=</span> tab<span class="token punctuation">.</span>length<span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span>            <span class="token comment" spellcheck="true">//【延迟初始化】</span>            tab <span class="token operator">=</span> <span class="token function">initTable</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token comment" spellcheck="true">// 【CASE2】：i 表示 key 使用【寻址算法】得到 key 对应数组的下标位置，tabAt 获取指定桶位的头结点f</span>        <span class="token comment" spellcheck="true">// i = (n - 1) &amp; hash 等价于 i = hash % n  </span>        <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>f <span class="token operator">=</span> <span class="token function">tabAt</span><span class="token punctuation">(</span>tab<span class="token punctuation">,</span> i <span class="token operator">=</span> <span class="token punctuation">(</span>n <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">&amp;</span> hash<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 对应的数组为 null 说明没有哈希冲突，直接新建节点添加到表中</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">casTabAt</span><span class="token punctuation">(</span>tab<span class="token punctuation">,</span> i<span class="token punctuation">,</span> null<span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name">Node</span><span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">(</span>hash<span class="token punctuation">,</span> key<span class="token punctuation">,</span> value<span class="token punctuation">,</span> null<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span>                <span class="token keyword">break</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 【CASE3】：逻辑说明数组已经被初始化，并且当前 key 对应的位置不为 null</span>        <span class="token comment" spellcheck="true">// 条件成立表示当前桶位的头结点为 FWD 结点，表示目前 map 正处于扩容过程中</span>        <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>fh <span class="token operator">=</span> f<span class="token punctuation">.</span>hash<span class="token punctuation">)</span> <span class="token operator">==</span> MOVED<span class="token punctuation">)</span>            <span class="token comment" spellcheck="true">// 当前线程【需要去帮助哈希表完成扩容】</span>            tab <span class="token operator">=</span> <span class="token function">helpTransfer</span><span class="token punctuation">(</span>tab<span class="token punctuation">,</span> f<span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token comment" spellcheck="true">// 【CASE4】：哈希表没有在扩容，当前桶位可能是链表也可能是红黑树</span>        <span class="token keyword">else</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 当插入 key 存在时，会将旧值赋值给 oldVal 返回</span>            V oldVal <span class="token operator">=</span> null<span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">// 【锁住当前 key 寻址的桶位的头节点】</span>            <span class="token keyword">synchronized</span> <span class="token punctuation">(</span>f<span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token comment" spellcheck="true">// 这里重新获取一下桶的头节点有没有被修改，因为可能被其他线程修改过，这里是线程安全的获取</span>                <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">tabAt</span><span class="token punctuation">(</span>tab<span class="token punctuation">,</span> i<span class="token punctuation">)</span> <span class="token operator">==</span> f<span class="token punctuation">)</span> <span class="token punctuation">{</span>                    <span class="token comment" spellcheck="true">// 【头节点的哈希值大于 0 说明当前桶位是普通的链表节点】</span>                    <span class="token keyword">if</span> <span class="token punctuation">(</span>fh <span class="token operator">>=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                        <span class="token comment" spellcheck="true">// 当前的插入操作没出现重复的 key，追加到链表的末尾，binCount表示链表长度 -1</span>                        <span class="token comment" spellcheck="true">// 插入的key与链表中的某个元素的 key 一致，变成替换操作，binCount 表示第几个节点冲突</span>                        binCount <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>                        <span class="token comment" spellcheck="true">// 迭代循环当前桶位的链表，e 是每次循环处理节点，e 初始是头节点</span>                        <span class="token keyword">for</span> <span class="token punctuation">(</span>Node<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> e <span class="token operator">=</span> f<span class="token punctuation">;</span><span class="token punctuation">;</span> <span class="token operator">++</span>binCount<span class="token punctuation">)</span> <span class="token punctuation">{</span>                            <span class="token comment" spellcheck="true">// 当前循环节点 key</span>                            K ek<span class="token punctuation">;</span>                            <span class="token comment" spellcheck="true">// key 的哈希值与当前节点的哈希一致，并且 key 的值也相同</span>                            <span class="token keyword">if</span> <span class="token punctuation">(</span>e<span class="token punctuation">.</span>hash <span class="token operator">==</span> hash <span class="token operator">&amp;&amp;</span>                                <span class="token punctuation">(</span><span class="token punctuation">(</span>ek <span class="token operator">=</span> e<span class="token punctuation">.</span>key<span class="token punctuation">)</span> <span class="token operator">==</span> key <span class="token operator">||</span>                                 <span class="token punctuation">(</span>ek <span class="token operator">!=</span> null <span class="token operator">&amp;&amp;</span> key<span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span>ek<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                                <span class="token comment" spellcheck="true">// 把当前节点的 value 赋值给 oldVal</span>                                oldVal <span class="token operator">=</span> e<span class="token punctuation">.</span>val<span class="token punctuation">;</span>                                <span class="token comment" spellcheck="true">// 允许覆盖</span>                                <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>onlyIfAbsent<span class="token punctuation">)</span>                                    <span class="token comment" spellcheck="true">// 新数据覆盖旧数据</span>                                    e<span class="token punctuation">.</span>val <span class="token operator">=</span> value<span class="token punctuation">;</span>                                <span class="token comment" spellcheck="true">// 跳出循环</span>                                <span class="token keyword">break</span><span class="token punctuation">;</span>                            <span class="token punctuation">}</span>                            Node<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> pred <span class="token operator">=</span> e<span class="token punctuation">;</span>                            <span class="token comment" spellcheck="true">// 如果下一个节点为空，把数据封装成节点插入链表尾部，【binCount 代表长度 - 1】</span>                            <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>e <span class="token operator">=</span> e<span class="token punctuation">.</span>next<span class="token punctuation">)</span> <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>                                pred<span class="token punctuation">.</span>next <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Node</span><span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">(</span>hash<span class="token punctuation">,</span> key<span class="token punctuation">,</span>                                                          value<span class="token punctuation">,</span> null<span class="token punctuation">)</span><span class="token punctuation">;</span>                                <span class="token keyword">break</span><span class="token punctuation">;</span>                            <span class="token punctuation">}</span>                        <span class="token punctuation">}</span>                    <span class="token punctuation">}</span>                    <span class="token comment" spellcheck="true">// 当前桶位头节点是红黑树</span>                    <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>f <span class="token keyword">instanceof</span> <span class="token class-name">TreeBin</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                        Node<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> p<span class="token punctuation">;</span>                        binCount <span class="token operator">=</span> <span class="token number">2</span><span class="token punctuation">;</span>                        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>p <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>TreeBin<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">)</span>f<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">putTreeVal</span><span class="token punctuation">(</span>hash<span class="token punctuation">,</span> key<span class="token punctuation">,</span>                                                              value<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>                            oldVal <span class="token operator">=</span> p<span class="token punctuation">.</span>val<span class="token punctuation">;</span>                            <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>onlyIfAbsent<span class="token punctuation">)</span>                                p<span class="token punctuation">.</span>val <span class="token operator">=</span> value<span class="token punctuation">;</span>                        <span class="token punctuation">}</span>                    <span class="token punctuation">}</span>                <span class="token punctuation">}</span>            <span class="token punctuation">}</span>                        <span class="token comment" spellcheck="true">// 条件成立说明当前是链表或者红黑树</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span>binCount <span class="token operator">!=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token comment" spellcheck="true">// 如果 binCount >= 8 表示处理的桶位一定是链表，说明长度是 9</span>                <span class="token keyword">if</span> <span class="token punctuation">(</span>binCount <span class="token operator">>=</span> TREEIFY_THRESHOLD<span class="token punctuation">)</span>                    <span class="token comment" spellcheck="true">// 树化</span>                    <span class="token function">treeifyBin</span><span class="token punctuation">(</span>tab<span class="token punctuation">,</span> i<span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token keyword">if</span> <span class="token punctuation">(</span>oldVal <span class="token operator">!=</span> null<span class="token punctuation">)</span>                    <span class="token keyword">return</span> oldVal<span class="token punctuation">;</span>                <span class="token keyword">break</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">// 统计当前 table 一共有多少数据，判断是否达到扩容阈值标准，触发扩容</span>    <span class="token comment" spellcheck="true">// binCount = 0 表示当前桶位为 null，node 可以直接放入，2 表示当前桶位已经是红黑树</span>    <span class="token function">addCount</span><span class="token punctuation">(</span>1L<span class="token punctuation">,</span> binCount<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> null<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>spread()：扰动函数</p><p>将 hashCode 无符号右移 16 位，高 16bit 和低 16bit 做异或，最后与 HASH_BITS 相与变成正数，<strong>与树化节点和转移节点区分</strong>，把高低位都利用起来减少哈希冲突，保证散列的均匀性</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> <span class="token function">spread</span><span class="token punctuation">(</span><span class="token keyword">int</span> h<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">return</span> <span class="token punctuation">(</span>h <span class="token operator">^</span> <span class="token punctuation">(</span>h <span class="token operator">>>></span> <span class="token number">16</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">&amp;</span> HASH_BITS<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 0111 1111 1111 1111 1111 1111 1111 1111</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre></li></ul><h4 id="初始化数组：initTable"><a href="#初始化数组：initTable" class="headerlink" title="初始化数组：initTable"></a>初始化数组：initTable</h4><p>这个比较简单，主要就是初始化一个合适大小的数组，然后会设置 sizeCtl。初始化方法中的并发问题是通过对 sizeCtl 进行一个 CAS 操作来控制的。</p><p>initTable()：初始化数组，延迟初始化</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">final</span> Node<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token function">initTable</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// tab 引用 map.table，sc 引用 sizeCtl</span>    Node<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span> tab<span class="token punctuation">;</span> <span class="token keyword">int</span> sc<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// table 尚未初始化，开始自旋</span>    <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>tab <span class="token operator">=</span> table<span class="token punctuation">)</span> <span class="token operator">==</span> null <span class="token operator">||</span> tab<span class="token punctuation">.</span>length <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// sc &lt; 0 说明 table 正在初始化或者正在扩容，当前线程可以释放 CPU 资源</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>sc <span class="token operator">=</span> sizeCtl<span class="token punctuation">)</span> <span class="token operator">&lt;</span> <span class="token number">0</span><span class="token punctuation">)</span>            Thread<span class="token punctuation">.</span><span class="token function">yield</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// sizeCtl 设置为 -1，相当于加锁，【设置的是 SIZECTL 位置的数据】，</span>        <span class="token comment" spellcheck="true">// 因为是 sizeCtl 是基本类型，不是引用类型，所以 sc 保存的是数据的副本</span>        <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>U<span class="token punctuation">.</span><span class="token function">compareAndSwapInt</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> SIZECTL<span class="token punctuation">,</span> sc<span class="token punctuation">,</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token keyword">try</span> <span class="token punctuation">{</span>                <span class="token comment" spellcheck="true">// 线程安全的逻辑，再进行一次判断</span>                <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>tab <span class="token operator">=</span> table<span class="token punctuation">)</span> <span class="token operator">==</span> null <span class="token operator">||</span> tab<span class="token punctuation">.</span>length <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                    <span class="token comment" spellcheck="true">// sc > 0 创建 table 时使用 sc 为指定大小，否则使用 16 默认值</span>                    <span class="token keyword">int</span> n <span class="token operator">=</span> <span class="token punctuation">(</span>sc <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token operator">?</span> sc <span class="token operator">:</span> DEFAULT_CAPACITY<span class="token punctuation">;</span>                    <span class="token comment" spellcheck="true">// 创建哈希表数组</span>                    Node<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span> nt <span class="token operator">=</span> <span class="token punctuation">(</span>Node<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token keyword">new</span> <span class="token class-name">Node</span><span class="token operator">&lt;</span><span class="token operator">?</span><span class="token punctuation">,</span><span class="token operator">?</span><span class="token operator">></span><span class="token punctuation">[</span>n<span class="token punctuation">]</span><span class="token punctuation">;</span>                    table <span class="token operator">=</span> tab <span class="token operator">=</span> nt<span class="token punctuation">;</span>                    <span class="token comment" spellcheck="true">// 扩容阈值，n >>> 2  => 等于 1/4 n ，n - (1/4)n = 3/4 n => 0.75 * n</span>                    sc <span class="token operator">=</span> n <span class="token operator">-</span> <span class="token punctuation">(</span>n <span class="token operator">>>></span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token punctuation">}</span>            <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>                <span class="token comment" spellcheck="true">// 解锁，把下一次扩容的阈值赋值给 sizeCtl</span>                sizeCtl <span class="token operator">=</span> sc<span class="token punctuation">;</span>            <span class="token punctuation">}</span>            <span class="token keyword">break</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span>    <span class="token keyword">return</span> tab<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="链表转树-treeifyBin"><a href="#链表转树-treeifyBin" class="headerlink" title="链表转树: treeifyBin"></a>链表转树: treeifyBin</h4><p>treeifyBin()：树化方法，链表转红黑树</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">final</span> <span class="token keyword">void</span> <span class="token function">treeifyBin</span><span class="token punctuation">(</span>Node<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span> tab<span class="token punctuation">,</span> <span class="token keyword">int</span> index<span class="token punctuation">)</span> <span class="token punctuation">{</span>    Node<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> b<span class="token punctuation">;</span> <span class="token keyword">int</span> n<span class="token punctuation">,</span> sc<span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>tab <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 条件成立：【说明当前 table 数组长度未达到 64，此时不进行树化操作，进行扩容操作】</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>n <span class="token operator">=</span> tab<span class="token punctuation">.</span>length<span class="token punctuation">)</span> <span class="token operator">&lt;</span> MIN_TREEIFY_CAPACITY<span class="token punctuation">)</span>            <span class="token comment" spellcheck="true">// 当前容量的 2 倍</span>            <span class="token function">tryPresize</span><span class="token punctuation">(</span>n <span class="token operator">&lt;&lt;</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 条件成立：说明当前桶位有数据，且是普通 node 数据。</span>        <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>b <span class="token operator">=</span> <span class="token function">tabAt</span><span class="token punctuation">(</span>tab<span class="token punctuation">,</span> index<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">!=</span> null <span class="token operator">&amp;&amp;</span> b<span class="token punctuation">.</span>hash <span class="token operator">>=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 【树化加锁】</span>            <span class="token keyword">synchronized</span> <span class="token punctuation">(</span>b<span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token comment" spellcheck="true">// 条件成立：表示加锁没问题。</span>                <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">tabAt</span><span class="token punctuation">(</span>tab<span class="token punctuation">,</span> index<span class="token punctuation">)</span> <span class="token operator">==</span> b<span class="token punctuation">)</span> <span class="token punctuation">{</span>                    TreeNode<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> hd <span class="token operator">=</span> null<span class="token punctuation">,</span> tl <span class="token operator">=</span> null<span class="token punctuation">;</span>                    <span class="token keyword">for</span> <span class="token punctuation">(</span>Node<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> e <span class="token operator">=</span> b<span class="token punctuation">;</span> e <span class="token operator">!=</span> null<span class="token punctuation">;</span> e <span class="token operator">=</span> e<span class="token punctuation">.</span>next<span class="token punctuation">)</span> <span class="token punctuation">{</span>                        TreeNode<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> p <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">TreeNode</span><span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>hash<span class="token punctuation">,</span> e<span class="token punctuation">.</span>key<span class="token punctuation">,</span> e<span class="token punctuation">.</span>val<span class="token punctuation">,</span>null<span class="token punctuation">,</span> null<span class="token punctuation">)</span><span class="token punctuation">;</span>                        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>p<span class="token punctuation">.</span>prev <span class="token operator">=</span> tl<span class="token punctuation">)</span> <span class="token operator">==</span> null<span class="token punctuation">)</span>                            hd <span class="token operator">=</span> p<span class="token punctuation">;</span>                        <span class="token keyword">else</span>                            tl<span class="token punctuation">.</span>next <span class="token operator">=</span> p<span class="token punctuation">;</span>                        tl <span class="token operator">=</span> p<span class="token punctuation">;</span>                    <span class="token punctuation">}</span>                    <span class="token function">setTabAt</span><span class="token punctuation">(</span>tab<span class="token punctuation">,</span> index<span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name">TreeBin</span><span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">(</span>hd<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token punctuation">}</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="扩容方法：tryPresize"><a href="#扩容方法：tryPresize" class="headerlink" title="扩容方法：tryPresize"></a>扩容方法：tryPresize</h4><p>扩容机制：</p><ul><li>当链表中元素个数超过 8 个，数组的大小还未超过 64 时，此时进行数组的扩容，如果超过则将链表转化成红黑树</li><li>put 数据后调用 addCount() 方法，判断当前哈希表的容量超过阈值 sizeCtl，超过进行扩容</li><li>增删改线程发现其他线程正在扩容，帮其扩容</li></ul><p>addCount()：添加计数，<strong>代表哈希表中的数据总量</strong></p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">final</span> <span class="token keyword">void</span> <span class="token function">addCount</span><span class="token punctuation">(</span><span class="token keyword">long</span> x<span class="token punctuation">,</span> <span class="token keyword">int</span> check<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 【上面这部分的逻辑就是 LongAdder 的累加逻辑】</span>    CounterCell<span class="token punctuation">[</span><span class="token punctuation">]</span> as<span class="token punctuation">;</span> <span class="token keyword">long</span> b<span class="token punctuation">,</span> s<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 判断累加数组 cells 是否初始化，没有就去累加 base 域，累加失败进入条件内逻辑</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>as <span class="token operator">=</span> counterCells<span class="token punctuation">)</span> <span class="token operator">!=</span> null <span class="token operator">||</span>        <span class="token operator">!</span>U<span class="token punctuation">.</span><span class="token function">compareAndSwapLong</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> BASECOUNT<span class="token punctuation">,</span> b <span class="token operator">=</span> baseCount<span class="token punctuation">,</span> s <span class="token operator">=</span> b <span class="token operator">+</span> x<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        CounterCell a<span class="token punctuation">;</span> <span class="token keyword">long</span> v<span class="token punctuation">;</span> <span class="token keyword">int</span> m<span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// true 未竞争，false 发生竞争</span>        <span class="token keyword">boolean</span> uncontended <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 判断 cells 是否被其他线程初始化</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>as <span class="token operator">==</span> null <span class="token operator">||</span> <span class="token punctuation">(</span>m <span class="token operator">=</span> as<span class="token punctuation">.</span>length <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">&lt;</span> <span class="token number">0</span> <span class="token operator">||</span>            <span class="token comment" spellcheck="true">// 前面的条件为 fasle 说明 cells 被其他线程初始化，通过 hash 寻址对应的槽位</span>            <span class="token punctuation">(</span>a <span class="token operator">=</span> as<span class="token punctuation">[</span>ThreadLocalRandom<span class="token punctuation">.</span><span class="token function">getProbe</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">&amp;</span> m<span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token operator">==</span> null <span class="token operator">||</span>            <span class="token comment" spellcheck="true">// 尝试去对应的槽位累加，累加失败进入 fullAddCount 进行重试或者扩容</span>            <span class="token operator">!</span><span class="token punctuation">(</span>uncontended <span class="token operator">=</span> U<span class="token punctuation">.</span><span class="token function">compareAndSwapLong</span><span class="token punctuation">(</span>a<span class="token punctuation">,</span> CELLVALUE<span class="token punctuation">,</span> v <span class="token operator">=</span> a<span class="token punctuation">.</span>value<span class="token punctuation">,</span> v <span class="token operator">+</span> x<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 与 Striped64#longAccumulate 方法相同</span>            <span class="token function">fullAddCount</span><span class="token punctuation">(</span>x<span class="token punctuation">,</span> uncontended<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">return</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 表示当前桶位是 null，或者一个链表节点</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>check <span class="token operator">&lt;=</span> <span class="token number">1</span><span class="token punctuation">)</span>            <span class="token keyword">return</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 【获取当前散列表元素个数】，这是一个期望值</span>        s <span class="token operator">=</span> <span class="token function">sumCount</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 表示一定 【是一个 put 操作调用的 addCount】</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>check <span class="token operator">>=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        Node<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span> tab<span class="token punctuation">,</span> nt<span class="token punctuation">;</span> <span class="token keyword">int</span> n<span class="token punctuation">,</span> sc<span class="token punctuation">;</span>                <span class="token comment" spellcheck="true">// 条件一：true 说明当前 sizeCtl 可能为一个负数表示正在扩容中，或者 sizeCtl 是一个正数，表示扩容阈值</span>        <span class="token comment" spellcheck="true">//        false 表示哈希表的数据的数量没达到扩容条件</span>        <span class="token comment" spellcheck="true">// 然后判断当前 table 数组是否初始化了，当前 table 长度是否小于最大值限制，就可以进行扩容</span>        <span class="token keyword">while</span> <span class="token punctuation">(</span>s <span class="token operator">>=</span> <span class="token punctuation">(</span><span class="token keyword">long</span><span class="token punctuation">)</span><span class="token punctuation">(</span>sc <span class="token operator">=</span> sizeCtl<span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> <span class="token punctuation">(</span>tab <span class="token operator">=</span> table<span class="token punctuation">)</span> <span class="token operator">!=</span> null <span class="token operator">&amp;&amp;</span>               <span class="token punctuation">(</span>n <span class="token operator">=</span> tab<span class="token punctuation">.</span>length<span class="token punctuation">)</span> <span class="token operator">&lt;</span> MAXIMUM_CAPACITY<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 16 -> 32 扩容 标识为：1000 0000 0001 1011，【负数，扩容批次唯一标识戳】</span>            <span class="token keyword">int</span> rs <span class="token operator">=</span> <span class="token function">resizeStamp</span><span class="token punctuation">(</span>n<span class="token punctuation">)</span><span class="token punctuation">;</span>                        <span class="token comment" spellcheck="true">// 表示当前 table，【正在扩容】，sc 高 16 位是扩容标识戳，低 16 位是线程数 + 1</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span>sc <span class="token operator">&lt;</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token comment" spellcheck="true">// 条件一：判断扩容标识戳是否一样，fasle 代表一样</span>                <span class="token comment" spellcheck="true">// 勘误两个条件：</span>                <span class="token comment" spellcheck="true">// 条件二是：sc == (rs &lt;&lt; 16 ) + 1，true 代表扩容完成，因为低16位是1代表没有线程扩容了</span>                <span class="token comment" spellcheck="true">// 条件三是：sc == (rs &lt;&lt; 16) + MAX_RESIZERS，判断是否已经超过最大允许的并发扩容线程数</span>                <span class="token comment" spellcheck="true">// 条件四：判断新表的引用是否是 null，代表扩容完成</span>                <span class="token comment" spellcheck="true">// 条件五：【扩容是从高位到低位转移】，transferIndex &lt; 0 说明没有区间需要扩容了</span>                <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>sc <span class="token operator">>>></span> RESIZE_STAMP_SHIFT<span class="token punctuation">)</span> <span class="token operator">!=</span> rs <span class="token operator">||</span> sc <span class="token operator">==</span> rs <span class="token operator">+</span> <span class="token number">1</span> <span class="token operator">||</span>                    sc <span class="token operator">==</span> rs <span class="token operator">+</span> MAX_RESIZERS <span class="token operator">||</span> <span class="token punctuation">(</span>nt <span class="token operator">=</span> nextTable<span class="token punctuation">)</span> <span class="token operator">==</span> null <span class="token operator">||</span>                    transferIndex <span class="token operator">&lt;=</span> <span class="token number">0</span><span class="token punctuation">)</span>                    <span class="token keyword">break</span><span class="token punctuation">;</span>                                <span class="token comment" spellcheck="true">// 设置当前线程参与到扩容任务中，将 sc 低 16 位值加 1，表示多一个线程参与扩容</span>                <span class="token comment" spellcheck="true">// 设置失败其他线程或者 transfer 内部修改了 sizeCtl 值</span>                <span class="token keyword">if</span> <span class="token punctuation">(</span>U<span class="token punctuation">.</span><span class="token function">compareAndSwapInt</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> SIZECTL<span class="token punctuation">,</span> sc<span class="token punctuation">,</span> sc <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span>                    <span class="token comment" spellcheck="true">//【协助扩容线程】，持有nextTable参数</span>                    <span class="token function">transfer</span><span class="token punctuation">(</span>tab<span class="token punctuation">,</span> nt<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>            <span class="token comment" spellcheck="true">// 逻辑到这说明当前线程是触发扩容的第一个线程，线程数量 + 2</span>            <span class="token comment" spellcheck="true">// 1000 0000 0001 1011 0000 0000 0000 0000 +2 => 1000 0000 0001 1011 0000 0000 0000 0010</span>            <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>U<span class="token punctuation">.</span><span class="token function">compareAndSwapInt</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> SIZECTL<span class="token punctuation">,</span> sc<span class="token punctuation">,</span><span class="token punctuation">(</span>rs <span class="token operator">&lt;&lt;</span> RESIZE_STAMP_SHIFT<span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">)</span>                <span class="token comment" spellcheck="true">//【触发扩容条件的线程】，不持有 nextTable，初始线程会新建 nextTable</span>                <span class="token function">transfer</span><span class="token punctuation">(</span>tab<span class="token punctuation">,</span> null<span class="token punctuation">)</span><span class="token punctuation">;</span>            s <span class="token operator">=</span> <span class="token function">sumCount</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>tryPresize方法的核心在于 sizeCtl 值的操作，首先将其设置为一个负数，然后执行 transfer(tab, null)，再下一个循环将 sizeCtl 加 1，并执行 transfer(tab, nt)，之后可能是继续 sizeCtl 加 1，并执行 transfer(tab, nt)。</p><p>所以，可能的操作就是执行 1 次 transfer(tab, null) + 多次 transfer(tab, nt)，这里怎么结束循环的需要看完 transfer 源码才清楚。</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// 首先要说明的是，方法参数 size 传进来的时候就已经翻了倍了</span><span class="token keyword">private</span> <span class="token keyword">final</span> <span class="token keyword">void</span> <span class="token function">tryPresize</span><span class="token punctuation">(</span><span class="token keyword">int</span> size<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// c: size 的 1.5 倍，再加 1，再往上取最近的 2 的 n 次方。</span>    <span class="token keyword">int</span> c <span class="token operator">=</span> <span class="token punctuation">(</span>size <span class="token operator">>=</span> <span class="token punctuation">(</span>MAXIMUM_CAPACITY <span class="token operator">>>></span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">?</span> MAXIMUM_CAPACITY <span class="token operator">:</span>        <span class="token function">tableSizeFor</span><span class="token punctuation">(</span>size <span class="token operator">+</span> <span class="token punctuation">(</span>size <span class="token operator">>>></span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">int</span> sc<span class="token punctuation">;</span>    <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>sc <span class="token operator">=</span> sizeCtl<span class="token punctuation">)</span> <span class="token operator">>=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        Node<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span> tab <span class="token operator">=</span> table<span class="token punctuation">;</span> <span class="token keyword">int</span> n<span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 这个 if 分支和之前说的初始化数组的代码基本上是一样的，在这里，我们可以不用管这块代码</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>tab <span class="token operator">==</span> null <span class="token operator">||</span> <span class="token punctuation">(</span>n <span class="token operator">=</span> tab<span class="token punctuation">.</span>length<span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            n <span class="token operator">=</span> <span class="token punctuation">(</span>sc <span class="token operator">></span> c<span class="token punctuation">)</span> <span class="token operator">?</span> sc <span class="token operator">:</span> c<span class="token punctuation">;</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span>U<span class="token punctuation">.</span><span class="token function">compareAndSwapInt</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> SIZECTL<span class="token punctuation">,</span> sc<span class="token punctuation">,</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token keyword">try</span> <span class="token punctuation">{</span>                    <span class="token keyword">if</span> <span class="token punctuation">(</span>table <span class="token operator">==</span> tab<span class="token punctuation">)</span> <span class="token punctuation">{</span>                        <span class="token annotation punctuation">@SuppressWarnings</span><span class="token punctuation">(</span><span class="token string">"unchecked"</span><span class="token punctuation">)</span>                        Node<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span> nt <span class="token operator">=</span> <span class="token punctuation">(</span>Node<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token keyword">new</span> <span class="token class-name">Node</span><span class="token operator">&lt;</span><span class="token operator">?</span><span class="token punctuation">,</span><span class="token operator">?</span><span class="token operator">></span><span class="token punctuation">[</span>n<span class="token punctuation">]</span><span class="token punctuation">;</span>                        table <span class="token operator">=</span> nt<span class="token punctuation">;</span>                        sc <span class="token operator">=</span> n <span class="token operator">-</span> <span class="token punctuation">(</span>n <span class="token operator">>>></span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 0.75 * n</span>                    <span class="token punctuation">}</span>                <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>                    sizeCtl <span class="token operator">=</span> sc<span class="token punctuation">;</span>                <span class="token punctuation">}</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span>        <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>c <span class="token operator">&lt;=</span> sc <span class="token operator">||</span> n <span class="token operator">>=</span> MAXIMUM_CAPACITY<span class="token punctuation">)</span>            <span class="token keyword">break</span><span class="token punctuation">;</span>        <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>tab <span class="token operator">==</span> table<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 扩容标识符</span>            <span class="token keyword">int</span> rs <span class="token operator">=</span> <span class="token function">resizeStamp</span><span class="token punctuation">(</span>n<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span>sc <span class="token operator">&lt;</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                Node<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span> nt<span class="token punctuation">;</span>                <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>sc <span class="token operator">>>></span> RESIZE_STAMP_SHIFT<span class="token punctuation">)</span> <span class="token operator">!=</span> rs <span class="token operator">||</span> sc <span class="token operator">==</span> rs <span class="token operator">+</span> <span class="token number">1</span> <span class="token operator">||</span>                    sc <span class="token operator">==</span> rs <span class="token operator">+</span> MAX_RESIZERS <span class="token operator">||</span> <span class="token punctuation">(</span>nt <span class="token operator">=</span> nextTable<span class="token punctuation">)</span> <span class="token operator">==</span> null <span class="token operator">||</span>                    transferIndex <span class="token operator">&lt;=</span> <span class="token number">0</span><span class="token punctuation">)</span>                    <span class="token keyword">break</span><span class="token punctuation">;</span>                <span class="token comment" spellcheck="true">// 2. 用 CAS 将 sizeCtl 加 1，然后执行 transfer 方法</span>                <span class="token comment" spellcheck="true">//    此时 nextTab 不为 null</span>                <span class="token keyword">if</span> <span class="token punctuation">(</span>U<span class="token punctuation">.</span><span class="token function">compareAndSwapInt</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> SIZECTL<span class="token punctuation">,</span> sc<span class="token punctuation">,</span> sc <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span>                    <span class="token function">transfer</span><span class="token punctuation">(</span>tab<span class="token punctuation">,</span> nt<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>            <span class="token comment" spellcheck="true">// 1. 将 sizeCtl 设置为 (rs &lt;&lt; RESIZE_STAMP_SHIFT) + 2)</span>            <span class="token comment" spellcheck="true">//     我是没看懂这个值真正的意义是什么? 不过可以计算出来的是，结果是一个比较大的负数</span>            <span class="token comment" spellcheck="true">//  调用 transfer 方法，此时 nextTab 参数为 null</span>            <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>U<span class="token punctuation">.</span><span class="token function">compareAndSwapInt</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> SIZECTL<span class="token punctuation">,</span> sc<span class="token punctuation">,</span>                                         <span class="token punctuation">(</span>rs <span class="token operator">&lt;&lt;</span> RESIZE_STAMP_SHIFT<span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">)</span>                <span class="token function">transfer</span><span class="token punctuation">(</span>tab<span class="token punctuation">,</span> null<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>resizeStamp()：扩容标识符，<strong>每次扩容都会产生一个，不是每个线程都产生</strong>，16 扩容到 32 产生一个，32 扩容到 64 产生一个</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">/** * 扩容的标识符 * 16 -> 32 从16扩容到32 * numberOfLeadingZeros(16) => 1 0000 => 32 - 5 = 27 => 0000 0000 0001 1011 * (1 &lt;&lt; (RESIZE_STAMP_BITS - 1)) => 1000 0000 0000 0000 => 32768 * --------------------------------------------------------------- * 0000 0000 0001 1011 * 1000 0000 0000 0000 * 1000 0000 0001 1011 * 永远是负数 */</span><span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> <span class="token function">resizeStamp</span><span class="token punctuation">(</span><span class="token keyword">int</span> n<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 或运算</span>    <span class="token keyword">return</span> Integer<span class="token punctuation">.</span><span class="token function">numberOfLeadingZeros</span><span class="token punctuation">(</span>n<span class="token punctuation">)</span> <span class="token operator">|</span> <span class="token punctuation">(</span><span class="token number">1</span> <span class="token operator">&lt;&lt;</span> <span class="token punctuation">(</span>RESIZE_STAMP_BITS <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// (16 -1 = 15)</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="数据迁移-transfer"><a href="#数据迁移-transfer" class="headerlink" title="数据迁移: transfer"></a>数据迁移: transfer</h4><p>transfer()：将数据转移到新表中，完成扩容</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">final</span> <span class="token keyword">void</span> <span class="token function">transfer</span><span class="token punctuation">(</span>Node<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span> tab<span class="token punctuation">,</span> Node<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span> nextTab<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// n 表示扩容之前 table 数组的长度</span>    <span class="token keyword">int</span> n <span class="token operator">=</span> tab<span class="token punctuation">.</span>length<span class="token punctuation">,</span> stride<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// stride 表示分配给线程任务的步长，默认就是 16 </span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>stride <span class="token operator">=</span> <span class="token punctuation">(</span>NCPU <span class="token operator">></span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token punctuation">(</span>n <span class="token operator">>>></span> <span class="token number">3</span><span class="token punctuation">)</span> <span class="token operator">/</span> NCPU <span class="token operator">:</span> n<span class="token punctuation">)</span> <span class="token operator">&lt;</span> MIN_TRANSFER_STRIDE<span class="token punctuation">)</span>        stride <span class="token operator">=</span> MIN_TRANSFER_STRIDE<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 如果当前线程为触发本次扩容的线程，需要做一些扩容准备工作，【协助线程不做这一步】</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>nextTab <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">try</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 创建一个容量是之前【二倍的 table 数组】</span>            Node<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span> nt <span class="token operator">=</span> <span class="token punctuation">(</span>Node<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token keyword">new</span> <span class="token class-name">Node</span><span class="token operator">&lt;</span><span class="token operator">?</span><span class="token punctuation">,</span><span class="token operator">?</span><span class="token operator">></span><span class="token punctuation">[</span>n <span class="token operator">&lt;&lt;</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">;</span>            nextTab <span class="token operator">=</span> nt<span class="token punctuation">;</span>        <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Throwable</span> ex<span class="token punctuation">)</span> <span class="token punctuation">{</span>            sizeCtl <span class="token operator">=</span> Integer<span class="token punctuation">.</span>MAX_VALUE<span class="token punctuation">;</span>            <span class="token keyword">return</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 把新表赋值给对象属性 nextTable，方便其他线程获取新表</span>        nextTable <span class="token operator">=</span> nextTab<span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 记录迁移数据整体位置的一个标记，transferIndex 计数从1开始不是 0，所以这里是长度，不是长度-1</span>        transferIndex <span class="token operator">=</span> n<span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">// 新数组的长度</span>    <span class="token keyword">int</span> nextn <span class="token operator">=</span> nextTab<span class="token punctuation">.</span>length<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 当某个桶位数据处理完毕后，将此桶位设置为 fwd 节点，其它写线程或读线程看到后，可以从中获取到新表</span>    ForwardingNode<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> fwd <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ForwardingNode</span><span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">(</span>nextTab<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 推进标记</span>    <span class="token keyword">boolean</span> advance <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 完成标记</span>    <span class="token keyword">boolean</span> finishing <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// i 表示分配给当前线程任务，执行到的桶位</span>    <span class="token comment" spellcheck="true">// bound 表示分配给当前线程任务的下界限制，因为是倒序迁移，16 迁移完 迁移 15，15完成去迁移14</span>    <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">,</span> bound <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span><span class="token punctuation">;</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        Node<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> f<span class="token punctuation">;</span> <span class="token keyword">int</span> fh<span class="token punctuation">;</span>                <span class="token comment" spellcheck="true">// 给当前线程【分配任务区间】</span>        <span class="token keyword">while</span> <span class="token punctuation">(</span>advance<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 分配任务的开始下标，分配任务的结束下标</span>            <span class="token keyword">int</span> nextIndex<span class="token punctuation">,</span> nextBound<span class="token punctuation">;</span>                     <span class="token comment" spellcheck="true">// --i 让当前线程处理下一个索引，true说明当前的迁移任务尚未完成，false说明线程已经完成或者还未分配</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">--</span>i <span class="token operator">>=</span> bound <span class="token operator">||</span> finishing<span class="token punctuation">)</span>                advance <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">// 迁移的开始下标，小于0说明没有区间需要迁移了，设置当前线程的 i 变量为 -1 跳出循环</span>            <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>nextIndex <span class="token operator">=</span> transferIndex<span class="token punctuation">)</span> <span class="token operator">&lt;=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                i <span class="token operator">=</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span>                advance <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>            <span class="token comment" spellcheck="true">// 逻辑到这说明还有区间需要分配，然后给当前线程分配任务，</span>            <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>U<span class="token punctuation">.</span><span class="token function">compareAndSwapInt</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> TRANSFERINDEX<span class="token punctuation">,</span> nextIndex<span class="token punctuation">,</span>                      <span class="token comment" spellcheck="true">// 判断区间是否还够一个步长，不够就全部分配</span>                      nextBound <span class="token operator">=</span> <span class="token punctuation">(</span>nextIndex <span class="token operator">></span> stride <span class="token operator">?</span> nextIndex <span class="token operator">-</span> stride <span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token comment" spellcheck="true">// 当前线程的结束下标</span>                bound <span class="token operator">=</span> nextBound<span class="token punctuation">;</span>                <span class="token comment" spellcheck="true">// 当前线程的开始下标，上一个线程结束的下标的下一个索引就是这个线程开始的下标</span>                i <span class="token operator">=</span> nextIndex <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">;</span>                <span class="token comment" spellcheck="true">// 任务分配结束，跳出循环执行迁移操作</span>                advance <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span>                <span class="token comment" spellcheck="true">// 【分配完成，开始数据迁移操作】</span>        <span class="token comment" spellcheck="true">// 【CASE1】：i &lt; 0 成立表示当前线程未分配到任务，或者任务执行完了</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>i <span class="token operator">&lt;</span> <span class="token number">0</span> <span class="token operator">||</span> i <span class="token operator">>=</span> n <span class="token operator">||</span> i <span class="token operator">+</span> n <span class="token operator">>=</span> nextn<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token keyword">int</span> sc<span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">// 如果迁移完成</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span>finishing<span class="token punctuation">)</span> <span class="token punctuation">{</span>                nextTable <span class="token operator">=</span> null<span class="token punctuation">;</span><span class="token comment" spellcheck="true">// help GC</span>                table <span class="token operator">=</span> nextTab<span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 新表赋值给当前对象</span>                sizeCtl <span class="token operator">=</span> <span class="token punctuation">(</span>n <span class="token operator">&lt;&lt;</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token punctuation">(</span>n <span class="token operator">>>></span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 扩容阈值为 2n - n/2 = 3n/2 = 0.75*(2n)</span>                <span class="token keyword">return</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>            <span class="token comment" spellcheck="true">// 当前线程完成了分配的任务区间，可以退出，先把 sizeCtl 赋值给 sc 保留</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span>U<span class="token punctuation">.</span><span class="token function">compareAndSwapInt</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> SIZECTL<span class="token punctuation">,</span> sc <span class="token operator">=</span> sizeCtl<span class="token punctuation">,</span> sc <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token comment" spellcheck="true">// 判断当前线程是不是最后一个线程，不是的话直接 return，</span>                <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>sc <span class="token operator">-</span> <span class="token number">2</span><span class="token punctuation">)</span> <span class="token operator">!=</span> <span class="token function">resizeStamp</span><span class="token punctuation">(</span>n<span class="token punctuation">)</span> <span class="token operator">&lt;&lt;</span> RESIZE_STAMP_SHIFT<span class="token punctuation">)</span>                    <span class="token keyword">return</span><span class="token punctuation">;</span>                <span class="token comment" spellcheck="true">// 所以最后一个线程退出的时候，sizeCtl 的低 16 位为 1</span>                finishing <span class="token operator">=</span> advance <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>                <span class="token comment" spellcheck="true">// 【这里表示最后一个线程需要重新检查一遍是否有漏掉的区间】</span>                i <span class="token operator">=</span> n<span class="token punctuation">;</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span>                <span class="token comment" spellcheck="true">// 【CASE2】：当前桶位未存放数据，只需要将此处设置为 fwd 节点即可。</span>        <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>f <span class="token operator">=</span> <span class="token function">tabAt</span><span class="token punctuation">(</span>tab<span class="token punctuation">,</span> i<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">==</span> null<span class="token punctuation">)</span>            advance <span class="token operator">=</span> <span class="token function">casTabAt</span><span class="token punctuation">(</span>tab<span class="token punctuation">,</span> i<span class="token punctuation">,</span> null<span class="token punctuation">,</span> fwd<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 【CASE3】：说明当前桶位已经迁移过了，当前线程不用再处理了，直接处理下一个桶位即可</span>        <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>fh <span class="token operator">=</span> f<span class="token punctuation">.</span>hash<span class="token punctuation">)</span> <span class="token operator">==</span> MOVED<span class="token punctuation">)</span>            advance <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>         <span class="token comment" spellcheck="true">// 【CASE4】：当前桶位有数据，而且 node 节点不是 fwd 节点，说明这些数据需要迁移</span>        <span class="token keyword">else</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 【锁住头节点】</span>            <span class="token keyword">synchronized</span> <span class="token punctuation">(</span>f<span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token comment" spellcheck="true">// 二次检查，防止头节点已经被修改了，因为这里才是线程安全的访问</span>                <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">tabAt</span><span class="token punctuation">(</span>tab<span class="token punctuation">,</span> i<span class="token punctuation">)</span> <span class="token operator">==</span> f<span class="token punctuation">)</span> <span class="token punctuation">{</span>                    <span class="token comment" spellcheck="true">// 【迁移数据的逻辑，和 HashMap 相似】</span>                                            <span class="token comment" spellcheck="true">// ln 表示低位链表引用</span>                    <span class="token comment" spellcheck="true">// hn 表示高位链表引用</span>                    Node<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> ln<span class="token punctuation">,</span> hn<span class="token punctuation">;</span>                    <span class="token comment" spellcheck="true">// 哈希 > 0 表示当前桶位是链表桶位</span>                    <span class="token keyword">if</span> <span class="token punctuation">(</span>fh <span class="token operator">>=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                        <span class="token comment" spellcheck="true">// 和 HashMap 的处理方式一致，与老数组长度相与，16 是 10000</span>                        <span class="token comment" spellcheck="true">// 判断对应的 1 的位置上是 0 或 1 分成高低位链表</span>                        <span class="token keyword">int</span> runBit <span class="token operator">=</span> fh <span class="token operator">&amp;</span> n<span class="token punctuation">;</span>                        Node<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> lastRun <span class="token operator">=</span> f<span class="token punctuation">;</span>                        <span class="token comment" spellcheck="true">// 遍历链表，寻找【逆序看】最长的对应位相同的链表，看下面的图更好的理解</span>                        <span class="token keyword">for</span> <span class="token punctuation">(</span>Node<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> p <span class="token operator">=</span> f<span class="token punctuation">.</span>next<span class="token punctuation">;</span> p <span class="token operator">!=</span> null<span class="token punctuation">;</span> p <span class="token operator">=</span> p<span class="token punctuation">.</span>next<span class="token punctuation">)</span> <span class="token punctuation">{</span>                            <span class="token comment" spellcheck="true">// 将当前节点的哈希 与 n</span>                            <span class="token keyword">int</span> b <span class="token operator">=</span> p<span class="token punctuation">.</span>hash <span class="token operator">&amp;</span> n<span class="token punctuation">;</span>                            <span class="token comment" spellcheck="true">// 如果当前值与前面节点的值 对应位 不同，则修改 runBit，把 lastRun 指向当前节点</span>                            <span class="token keyword">if</span> <span class="token punctuation">(</span>b <span class="token operator">!=</span> runBit<span class="token punctuation">)</span> <span class="token punctuation">{</span>                                runBit <span class="token operator">=</span> b<span class="token punctuation">;</span>                                lastRun <span class="token operator">=</span> p<span class="token punctuation">;</span>                            <span class="token punctuation">}</span>                        <span class="token punctuation">}</span>                        <span class="token comment" spellcheck="true">// 判断筛选出的链表是低位的还是高位的</span>                        <span class="token keyword">if</span> <span class="token punctuation">(</span>runBit <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                            ln <span class="token operator">=</span> lastRun<span class="token punctuation">;</span><span class="token comment" spellcheck="true">// ln 指向该链表</span>                            hn <span class="token operator">=</span> null<span class="token punctuation">;</span><span class="token comment" spellcheck="true">// hn 为 null</span>                        <span class="token punctuation">}</span>                        <span class="token comment" spellcheck="true">// 说明 lastRun 引用的链表为高位链表，就让 hn 指向高位链表头节点</span>                        <span class="token keyword">else</span> <span class="token punctuation">{</span>                            hn <span class="token operator">=</span> lastRun<span class="token punctuation">;</span>                            ln <span class="token operator">=</span> null<span class="token punctuation">;</span>                        <span class="token punctuation">}</span>                        <span class="token comment" spellcheck="true">// 从头开始遍历所有的链表节点，迭代到 p == lastRun 节点跳出循环</span>                        <span class="token keyword">for</span> <span class="token punctuation">(</span>Node<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> p <span class="token operator">=</span> f<span class="token punctuation">;</span> p <span class="token operator">!=</span> lastRun<span class="token punctuation">;</span> p <span class="token operator">=</span> p<span class="token punctuation">.</span>next<span class="token punctuation">)</span> <span class="token punctuation">{</span>                            <span class="token keyword">int</span> ph <span class="token operator">=</span> p<span class="token punctuation">.</span>hash<span class="token punctuation">;</span> K pk <span class="token operator">=</span> p<span class="token punctuation">.</span>key<span class="token punctuation">;</span> V pv <span class="token operator">=</span> p<span class="token punctuation">.</span>val<span class="token punctuation">;</span>                            <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>ph <span class="token operator">&amp;</span> n<span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span>                                <span class="token comment" spellcheck="true">// 【头插法】，从右往左看，首先 ln 指向的是上一个节点，</span>                                <span class="token comment" spellcheck="true">// 所以这次新建的节点的 next 指向上一个节点，然后更新 ln 的引用</span>                                ln <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Node</span><span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">(</span>ph<span class="token punctuation">,</span> pk<span class="token punctuation">,</span> pv<span class="token punctuation">,</span> ln<span class="token punctuation">)</span><span class="token punctuation">;</span>                            <span class="token keyword">else</span>                                hn <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Node</span><span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">(</span>ph<span class="token punctuation">,</span> pk<span class="token punctuation">,</span> pv<span class="token punctuation">,</span> hn<span class="token punctuation">)</span><span class="token punctuation">;</span>                        <span class="token punctuation">}</span>                        <span class="token comment" spellcheck="true">// 高低位链设置到新表中的指定位置</span>                        <span class="token function">setTabAt</span><span class="token punctuation">(</span>nextTab<span class="token punctuation">,</span> i<span class="token punctuation">,</span> ln<span class="token punctuation">)</span><span class="token punctuation">;</span>                        <span class="token function">setTabAt</span><span class="token punctuation">(</span>nextTab<span class="token punctuation">,</span> i <span class="token operator">+</span> n<span class="token punctuation">,</span> hn<span class="token punctuation">)</span><span class="token punctuation">;</span>                        <span class="token comment" spellcheck="true">// 老表中的该桶位设置为 fwd 节点</span>                        <span class="token function">setTabAt</span><span class="token punctuation">(</span>tab<span class="token punctuation">,</span> i<span class="token punctuation">,</span> fwd<span class="token punctuation">)</span><span class="token punctuation">;</span>                        advance <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>                    <span class="token punctuation">}</span>                    <span class="token comment" spellcheck="true">// 条件成立：表示当前桶位是 红黑树结点</span>                    <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>f <span class="token keyword">instanceof</span> <span class="token class-name">TreeBin</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                        TreeBin<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> t <span class="token operator">=</span> <span class="token punctuation">(</span>TreeBin<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">)</span>f<span class="token punctuation">;</span>                        TreeNode<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> lo <span class="token operator">=</span> null<span class="token punctuation">,</span> loTail <span class="token operator">=</span> null<span class="token punctuation">;</span>                        TreeNode<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> hi <span class="token operator">=</span> null<span class="token punctuation">,</span> hiTail <span class="token operator">=</span> null<span class="token punctuation">;</span>                        <span class="token keyword">int</span> lc <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">,</span> hc <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>                        <span class="token comment" spellcheck="true">// 迭代 TreeBin 中的双向链表，从头结点至尾节点</span>                        <span class="token keyword">for</span> <span class="token punctuation">(</span>Node<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> e <span class="token operator">=</span> t<span class="token punctuation">.</span>first<span class="token punctuation">;</span> e <span class="token operator">!=</span> null<span class="token punctuation">;</span> e <span class="token operator">=</span> e<span class="token punctuation">.</span>next<span class="token punctuation">)</span> <span class="token punctuation">{</span>                            <span class="token comment" spellcheck="true">// 迭代的当前元素的 hash</span>                            <span class="token keyword">int</span> h <span class="token operator">=</span> e<span class="token punctuation">.</span>hash<span class="token punctuation">;</span>                            TreeNode<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> p <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">TreeNode</span><span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span>                                <span class="token punctuation">(</span>h<span class="token punctuation">,</span> e<span class="token punctuation">.</span>key<span class="token punctuation">,</span> e<span class="token punctuation">.</span>val<span class="token punctuation">,</span> null<span class="token punctuation">,</span> null<span class="token punctuation">)</span><span class="token punctuation">;</span>                            <span class="token comment" spellcheck="true">// 条件成立表示当前循环节点属于低位链节点</span>                            <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>h <span class="token operator">&amp;</span> n<span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                                <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>p<span class="token punctuation">.</span>prev <span class="token operator">=</span> loTail<span class="token punctuation">)</span> <span class="token operator">==</span> null<span class="token punctuation">)</span>                                    lo <span class="token operator">=</span> p<span class="token punctuation">;</span>                                <span class="token keyword">else</span>                                    <span class="token comment" spellcheck="true">//【尾插法】</span>                                    loTail<span class="token punctuation">.</span>next <span class="token operator">=</span> p<span class="token punctuation">;</span>                                <span class="token comment" spellcheck="true">// loTail 指向尾节点</span>                                loTail <span class="token operator">=</span> p<span class="token punctuation">;</span>                                <span class="token operator">++</span>lc<span class="token punctuation">;</span>                            <span class="token punctuation">}</span>                            <span class="token keyword">else</span> <span class="token punctuation">{</span>                                <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>p<span class="token punctuation">.</span>prev <span class="token operator">=</span> hiTail<span class="token punctuation">)</span> <span class="token operator">==</span> null<span class="token punctuation">)</span>                                    hi <span class="token operator">=</span> p<span class="token punctuation">;</span>                                <span class="token keyword">else</span>                                    hiTail<span class="token punctuation">.</span>next <span class="token operator">=</span> p<span class="token punctuation">;</span>                                hiTail <span class="token operator">=</span> p<span class="token punctuation">;</span>                                <span class="token operator">++</span>hc<span class="token punctuation">;</span>                            <span class="token punctuation">}</span>                        <span class="token punctuation">}</span>                        <span class="token comment" spellcheck="true">// 拆成的高位低位两个链，【判断是否需要需要转化为链表】，反之保持树化</span>                        ln <span class="token operator">=</span> <span class="token punctuation">(</span>lc <span class="token operator">&lt;=</span> UNTREEIFY_THRESHOLD<span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token function">untreeify</span><span class="token punctuation">(</span>lo<span class="token punctuation">)</span> <span class="token operator">:</span>                        <span class="token punctuation">(</span>hc <span class="token operator">!=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token keyword">new</span> <span class="token class-name">TreeBin</span><span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">(</span>lo<span class="token punctuation">)</span> <span class="token operator">:</span> t<span class="token punctuation">;</span>                        hn <span class="token operator">=</span> <span class="token punctuation">(</span>hc <span class="token operator">&lt;=</span> UNTREEIFY_THRESHOLD<span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token function">untreeify</span><span class="token punctuation">(</span>hi<span class="token punctuation">)</span> <span class="token operator">:</span>                        <span class="token punctuation">(</span>lc <span class="token operator">!=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token keyword">new</span> <span class="token class-name">TreeBin</span><span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">(</span>hi<span class="token punctuation">)</span> <span class="token operator">:</span> t<span class="token punctuation">;</span>                        <span class="token function">setTabAt</span><span class="token punctuation">(</span>nextTab<span class="token punctuation">,</span> i<span class="token punctuation">,</span> ln<span class="token punctuation">)</span><span class="token punctuation">;</span>                        <span class="token function">setTabAt</span><span class="token punctuation">(</span>nextTab<span class="token punctuation">,</span> i <span class="token operator">+</span> n<span class="token punctuation">,</span> hn<span class="token punctuation">)</span><span class="token punctuation">;</span>                        <span class="token function">setTabAt</span><span class="token punctuation">(</span>tab<span class="token punctuation">,</span> i<span class="token punctuation">,</span> fwd<span class="token punctuation">)</span><span class="token punctuation">;</span>                        advance <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>                    <span class="token punctuation">}</span>                <span class="token punctuation">}</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>helpTransfer()：帮助扩容机制</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">final</span> Node<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token function">helpTransfer</span><span class="token punctuation">(</span>Node<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span> tab<span class="token punctuation">,</span> Node<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> f<span class="token punctuation">)</span> <span class="token punctuation">{</span>    Node<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span> nextTab<span class="token punctuation">;</span> <span class="token keyword">int</span> sc<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 数组不为空，节点是转发节点，获取转发节点指向的新表开始协助主线程扩容</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>tab <span class="token operator">!=</span> null <span class="token operator">&amp;&amp;</span> <span class="token punctuation">(</span>f <span class="token keyword">instanceof</span> <span class="token class-name">ForwardingNode</span><span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span>        <span class="token punctuation">(</span>nextTab <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>ForwardingNode<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">)</span>f<span class="token punctuation">)</span><span class="token punctuation">.</span>nextTable<span class="token punctuation">)</span> <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 扩容标识戳</span>        <span class="token keyword">int</span> rs <span class="token operator">=</span> <span class="token function">resizeStamp</span><span class="token punctuation">(</span>tab<span class="token punctuation">.</span>length<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 判断数据迁移是否完成，迁移完成会把 新表赋值给 nextTable 属性</span>        <span class="token keyword">while</span> <span class="token punctuation">(</span>nextTab <span class="token operator">==</span> nextTable <span class="token operator">&amp;&amp;</span> table <span class="token operator">==</span> tab <span class="token operator">&amp;&amp;</span> <span class="token punctuation">(</span>sc <span class="token operator">=</span> sizeCtl<span class="token punctuation">)</span> <span class="token operator">&lt;</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>sc <span class="token operator">>>></span> RESIZE_STAMP_SHIFT<span class="token punctuation">)</span> <span class="token operator">!=</span> rs <span class="token operator">||</span> sc <span class="token operator">==</span> rs <span class="token operator">+</span> <span class="token number">1</span> <span class="token operator">||</span>                sc <span class="token operator">==</span> rs <span class="token operator">+</span> MAX_RESIZERS <span class="token operator">||</span> transferIndex <span class="token operator">&lt;=</span> <span class="token number">0</span><span class="token punctuation">)</span>                <span class="token keyword">break</span><span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">// 设置扩容线程数量 + 1</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span>U<span class="token punctuation">.</span><span class="token function">compareAndSwapInt</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> SIZECTL<span class="token punctuation">,</span> sc<span class="token punctuation">,</span> sc <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token comment" spellcheck="true">// 协助扩容</span>                <span class="token function">transfer</span><span class="token punctuation">(</span>tab<span class="token punctuation">,</span> nextTab<span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token keyword">break</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> nextTab<span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">return</span> table<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="获取方法：get"><a href="#获取方法：get" class="headerlink" title="获取方法：get"></a>获取方法：get</h4><ul><li>计算 hash 值</li><li>根据 hash 值找到数组对应位置: (n - 1) &amp; h  等价于 h % n</li><li>根据该位置处结点性质进行相应查找 <ul><li>如果该位置为 null，那么直接返回 null 就可以了</li><li>如果该位置处的节点刚好就是我们需要的，返回该节点的值即可</li><li>如果该位置节点的 hash 值小于 0，说明正在扩容，或者是红黑树</li><li>如果以上 3 条都不满足，那就是链表，进行遍历比对即可</li></ul></li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> V <span class="token function">get</span><span class="token punctuation">(</span>Object key<span class="token punctuation">)</span> <span class="token punctuation">{</span>    Node<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span> tab<span class="token punctuation">;</span> Node<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> e<span class="token punctuation">,</span> p<span class="token punctuation">;</span> <span class="token keyword">int</span> n<span class="token punctuation">,</span> eh<span class="token punctuation">;</span> K ek<span class="token punctuation">;</span>    <span class="token keyword">int</span> h <span class="token operator">=</span> <span class="token function">spread</span><span class="token punctuation">(</span>key<span class="token punctuation">.</span><span class="token function">hashCode</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>tab <span class="token operator">=</span> table<span class="token punctuation">)</span> <span class="token operator">!=</span> null <span class="token operator">&amp;&amp;</span> <span class="token punctuation">(</span>n <span class="token operator">=</span> tab<span class="token punctuation">.</span>length<span class="token punctuation">)</span> <span class="token operator">></span> <span class="token number">0</span> <span class="token operator">&amp;&amp;</span>        <span class="token punctuation">(</span>e <span class="token operator">=</span> <span class="token function">tabAt</span><span class="token punctuation">(</span>tab<span class="token punctuation">,</span> <span class="token punctuation">(</span>n <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">&amp;</span> h<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 判断头节点是否就是我们需要的节点</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>eh <span class="token operator">=</span> e<span class="token punctuation">.</span>hash<span class="token punctuation">)</span> <span class="token operator">==</span> h<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>ek <span class="token operator">=</span> e<span class="token punctuation">.</span>key<span class="token punctuation">)</span> <span class="token operator">==</span> key <span class="token operator">||</span> <span class="token punctuation">(</span>ek <span class="token operator">!=</span> null <span class="token operator">&amp;&amp;</span> key<span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span>ek<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span>                <span class="token keyword">return</span> e<span class="token punctuation">.</span>val<span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 如果头节点的 hash 小于 0，说明 正在扩容，或者该位置是红黑树</span>        <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>eh <span class="token operator">&lt;</span> <span class="token number">0</span><span class="token punctuation">)</span>            <span class="token comment" spellcheck="true">// 参考 ForwardingNode.find(int h, Object k) 和 TreeBin.find(int h, Object k)</span>            <span class="token keyword">return</span> <span class="token punctuation">(</span>p <span class="token operator">=</span> e<span class="token punctuation">.</span><span class="token function">find</span><span class="token punctuation">(</span>h<span class="token punctuation">,</span> key<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">!=</span> null <span class="token operator">?</span> p<span class="token punctuation">.</span>val <span class="token operator">:</span> null<span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 遍历链表</span>        <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>e <span class="token operator">=</span> e<span class="token punctuation">.</span>next<span class="token punctuation">)</span> <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span>e<span class="token punctuation">.</span>hash <span class="token operator">==</span> h <span class="token operator">&amp;&amp;</span>                <span class="token punctuation">(</span><span class="token punctuation">(</span>ek <span class="token operator">=</span> e<span class="token punctuation">.</span>key<span class="token punctuation">)</span> <span class="token operator">==</span> key <span class="token operator">||</span> <span class="token punctuation">(</span>ek <span class="token operator">!=</span> null <span class="token operator">&amp;&amp;</span> key<span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span>ek<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span>                <span class="token keyword">return</span> e<span class="token punctuation">.</span>val<span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span>    <span class="token keyword">return</span> null<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="删除方法：remove"><a href="#删除方法：remove" class="headerlink" title="删除方法：remove"></a>删除方法：remove</h4><ul><li><p>remove()：删除指定元素</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> V <span class="token function">remove</span><span class="token punctuation">(</span>Object key<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">return</span> <span class="token function">replaceNode</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> null<span class="token punctuation">,</span> null<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre></li><li><p>replaceNode()：替代指定的元素，会协助扩容，<strong>增删改（写）都会协助扩容，查询（读）操作不会</strong>，因为读操作不涉及加锁</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">final</span> V <span class="token function">replaceNode</span><span class="token punctuation">(</span>Object key<span class="token punctuation">,</span> V value<span class="token punctuation">,</span> Object cv<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 计算 key 扰动运算后的 hash</span>    <span class="token keyword">int</span> hash <span class="token operator">=</span> <span class="token function">spread</span><span class="token punctuation">(</span>key<span class="token punctuation">.</span><span class="token function">hashCode</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 开始自旋</span>    <span class="token keyword">for</span> <span class="token punctuation">(</span>Node<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span> tab <span class="token operator">=</span> table<span class="token punctuation">;</span><span class="token punctuation">;</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        Node<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> f<span class="token punctuation">;</span> <span class="token keyword">int</span> n<span class="token punctuation">,</span> i<span class="token punctuation">,</span> fh<span class="token punctuation">;</span>                <span class="token comment" spellcheck="true">// 【CASE1】：table 还未初始化或者哈希寻址的数组索引处为 null，直接结束自旋，返回 null</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>tab <span class="token operator">==</span> null <span class="token operator">||</span> <span class="token punctuation">(</span>n <span class="token operator">=</span> tab<span class="token punctuation">.</span>length<span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token number">0</span> <span class="token operator">||</span> <span class="token punctuation">(</span>f <span class="token operator">=</span> <span class="token function">tabAt</span><span class="token punctuation">(</span>tab<span class="token punctuation">,</span> i <span class="token operator">=</span> <span class="token punctuation">(</span>n <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">&amp;</span> hash<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">==</span> null<span class="token punctuation">)</span>            <span class="token keyword">break</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 【CASE2】：条件成立说明当前 table 正在扩容，【当前是个写操作，所以当前线程需要协助 table 完成扩容】</span>        <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>fh <span class="token operator">=</span> f<span class="token punctuation">.</span>hash<span class="token punctuation">)</span> <span class="token operator">==</span> MOVED<span class="token punctuation">)</span>            tab <span class="token operator">=</span> <span class="token function">helpTransfer</span><span class="token punctuation">(</span>tab<span class="token punctuation">,</span> f<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 【CASE3】：当前桶位可能是 链表 也可能是 红黑树 </span>        <span class="token keyword">else</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 保留替换之前数据引用</span>            V oldVal <span class="token operator">=</span> null<span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">// 校验标记</span>            <span class="token keyword">boolean</span> validated <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">// 【加锁当前桶位头结点】，加锁成功之后会进入代码块</span>            <span class="token keyword">synchronized</span> <span class="token punctuation">(</span>f<span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token comment" spellcheck="true">// 双重检查</span>                <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">tabAt</span><span class="token punctuation">(</span>tab<span class="token punctuation">,</span> i<span class="token punctuation">)</span> <span class="token operator">==</span> f<span class="token punctuation">)</span> <span class="token punctuation">{</span>                    <span class="token comment" spellcheck="true">// 说明当前节点是链表节点</span>                    <span class="token keyword">if</span> <span class="token punctuation">(</span>fh <span class="token operator">>=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                        validated <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>                        <span class="token comment" spellcheck="true">//遍历所有的节点</span>                        <span class="token keyword">for</span> <span class="token punctuation">(</span>Node<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> e <span class="token operator">=</span> f<span class="token punctuation">,</span> pred <span class="token operator">=</span> null<span class="token punctuation">;</span><span class="token punctuation">;</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                            K ek<span class="token punctuation">;</span>                            <span class="token comment" spellcheck="true">// hash 和值都相同，定位到了具体的节点</span>                            <span class="token keyword">if</span> <span class="token punctuation">(</span>e<span class="token punctuation">.</span>hash <span class="token operator">==</span> hash <span class="token operator">&amp;&amp;</span>                                <span class="token punctuation">(</span><span class="token punctuation">(</span>ek <span class="token operator">=</span> e<span class="token punctuation">.</span>key<span class="token punctuation">)</span> <span class="token operator">==</span> key <span class="token operator">||</span>                                 <span class="token punctuation">(</span>ek <span class="token operator">!=</span> null <span class="token operator">&amp;&amp;</span> key<span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span>ek<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                                <span class="token comment" spellcheck="true">// 当前节点的value</span>                                V ev <span class="token operator">=</span> e<span class="token punctuation">.</span>val<span class="token punctuation">;</span>                                <span class="token keyword">if</span> <span class="token punctuation">(</span>cv <span class="token operator">==</span> null <span class="token operator">||</span> cv <span class="token operator">==</span> ev <span class="token operator">||</span>                                    <span class="token punctuation">(</span>ev <span class="token operator">!=</span> null <span class="token operator">&amp;&amp;</span> cv<span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span>ev<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                                    <span class="token comment" spellcheck="true">// 将当前节点的值 赋值给 oldVal 后续返回会用到</span>                                    oldVal <span class="token operator">=</span> ev<span class="token punctuation">;</span>                                    <span class="token keyword">if</span> <span class="token punctuation">(</span>value <span class="token operator">!=</span> null<span class="token punctuation">)</span><span class="token comment" spellcheck="true">// 条件成立说明是替换操作</span>                                        e<span class="token punctuation">.</span>val <span class="token operator">=</span> value<span class="token punctuation">;</span>                                    <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>pred <span class="token operator">!=</span> null<span class="token punctuation">)</span><span class="token comment" spellcheck="true">// 非头节点删除操作，断开链表</span>                                        pred<span class="token punctuation">.</span>next <span class="token operator">=</span> e<span class="token punctuation">.</span>next<span class="token punctuation">;</span>                                    <span class="token keyword">else</span>                                        <span class="token comment" spellcheck="true">// 说明当前节点即为头结点，将桶位头节点设置为以前头节点的下一个节点</span>                                        <span class="token function">setTabAt</span><span class="token punctuation">(</span>tab<span class="token punctuation">,</span> i<span class="token punctuation">,</span> e<span class="token punctuation">.</span>next<span class="token punctuation">)</span><span class="token punctuation">;</span>                                <span class="token punctuation">}</span>                                <span class="token keyword">break</span><span class="token punctuation">;</span>                            <span class="token punctuation">}</span>                            pred <span class="token operator">=</span> e<span class="token punctuation">;</span>                            <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>e <span class="token operator">=</span> e<span class="token punctuation">.</span>next<span class="token punctuation">)</span> <span class="token operator">==</span> null<span class="token punctuation">)</span>                                <span class="token keyword">break</span><span class="token punctuation">;</span>                        <span class="token punctuation">}</span>                    <span class="token punctuation">}</span>                    <span class="token comment" spellcheck="true">// 说明是红黑树节点</span>                    <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>f <span class="token keyword">instanceof</span> <span class="token class-name">TreeBin</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                        validated <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>                        TreeBin<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> t <span class="token operator">=</span> <span class="token punctuation">(</span>TreeBin<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span><span class="token punctuation">)</span>f<span class="token punctuation">;</span>                        TreeNode<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> r<span class="token punctuation">,</span> p<span class="token punctuation">;</span>                        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>r <span class="token operator">=</span> t<span class="token punctuation">.</span>root<span class="token punctuation">)</span> <span class="token operator">!=</span> null <span class="token operator">&amp;&amp;</span>                            <span class="token punctuation">(</span>p <span class="token operator">=</span> r<span class="token punctuation">.</span><span class="token function">findTreeNode</span><span class="token punctuation">(</span>hash<span class="token punctuation">,</span> key<span class="token punctuation">,</span> null<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>                            V pv <span class="token operator">=</span> p<span class="token punctuation">.</span>val<span class="token punctuation">;</span>                            <span class="token keyword">if</span> <span class="token punctuation">(</span>cv <span class="token operator">==</span> null <span class="token operator">||</span> cv <span class="token operator">==</span> pv <span class="token operator">||</span>                                <span class="token punctuation">(</span>pv <span class="token operator">!=</span> null <span class="token operator">&amp;&amp;</span> cv<span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span>pv<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                                oldVal <span class="token operator">=</span> pv<span class="token punctuation">;</span>                                <span class="token comment" spellcheck="true">// 条件成立说明替换操作</span>                                <span class="token keyword">if</span> <span class="token punctuation">(</span>value <span class="token operator">!=</span> null<span class="token punctuation">)</span>                                    p<span class="token punctuation">.</span>val <span class="token operator">=</span> value<span class="token punctuation">;</span>                                <span class="token comment" spellcheck="true">// 删除操作</span>                                <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>t<span class="token punctuation">.</span><span class="token function">removeTreeNode</span><span class="token punctuation">(</span>p<span class="token punctuation">)</span><span class="token punctuation">)</span>                                    <span class="token function">setTabAt</span><span class="token punctuation">(</span>tab<span class="token punctuation">,</span> i<span class="token punctuation">,</span> <span class="token function">untreeify</span><span class="token punctuation">(</span>t<span class="token punctuation">.</span>first<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                            <span class="token punctuation">}</span>                        <span class="token punctuation">}</span>                    <span class="token punctuation">}</span>                <span class="token punctuation">}</span>            <span class="token punctuation">}</span>            <span class="token comment" spellcheck="true">// 其他线程修改过桶位头结点时，当前线程 sync 头结点锁错对象，validated 为 false，会进入下次 for 自旋</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span>validated<span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token keyword">if</span> <span class="token punctuation">(</span>oldVal <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>                    <span class="token comment" spellcheck="true">// 替换的值为 null，【说明当前是一次删除操作，更新当前元素个数计数器】</span>                    <span class="token keyword">if</span> <span class="token punctuation">(</span>value <span class="token operator">==</span> null<span class="token punctuation">)</span>                        <span class="token function">addCount</span><span class="token punctuation">(</span><span class="token operator">-</span>1L<span class="token punctuation">,</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                    <span class="token keyword">return</span> oldVal<span class="token punctuation">;</span>                <span class="token punctuation">}</span>                <span class="token keyword">break</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span>    <span class="token keyword">return</span> null<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li></ul><h3 id="JDK7原理"><a href="#JDK7原理" class="headerlink" title="JDK7原理"></a>JDK7原理</h3><p>ConcurrentHashMap 对锁粒度进行了优化，<strong>分段锁技术</strong>，将整张表分成了多个数组（Segment），每个数组又是一个类似 HashMap 数组的结构。允许多个修改操作并发进行，Segment 是一种可重入锁，继承 ReentrantLock，并发时锁住的是每个 Segment，其他 Segment 还是可以操作的，这样不同 Segment 之间就可以实现并发，大大提高效率。</p><p>底层结构： <strong>Segment 数组 + HashEntry 数组 + 链表</strong>（数组 + 链表是 HashMap 的结构）</p><ul><li><p>优点：如果多个线程访问不同的 segment，实际是没有冲突的，这与 JDK8 中是类似的</p></li><li><p>缺点：Segments 数组默认大小为16，这个容量初始化指定后就不能改变了，并且不是懒惰初始化</p></li></ul><p><img src="https://img.jwt1399.top/img/202301071534470.png"></p><h2 id="❷CopyOnWriteArrayList"><a href="#❷CopyOnWriteArrayList" class="headerlink" title="❷CopyOnWriteArrayList"></a>❷CopyOnWriteArrayList</h2><h3 id="原理分析-1"><a href="#原理分析-1" class="headerlink" title="原理分析"></a>原理分析</h3><p>CopyOnWriteArrayList 采用了<strong>写入时拷贝</strong>的思想，增删改操作会将底层数组拷贝一份，在新数组上执行操作，不影响其它线程的<strong>并发读，读写分离</strong></p><p>CopyOnWriteArraySet 底层对 CopyOnWriteArrayList 进行了包装，装饰器模式</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token function">CopyOnWriteArraySet</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    al <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">CopyOnWriteArrayList</span><span class="token operator">&lt;</span>E<span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><ul><li><p>存储结构：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">transient</span> <span class="token keyword">volatile</span> Object<span class="token punctuation">[</span><span class="token punctuation">]</span> array<span class="token punctuation">;</span><span class="token comment" spellcheck="true">// volatile 保证了读写线程之间的可见性</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre></li><li><p>全局锁：保证线程的执行安全</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">final</span> <span class="token keyword">transient</span> ReentrantLock lock <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ReentrantLock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre></li><li><p>新增数据：需要加锁，<strong>创建新的数组操作</strong></p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">boolean</span> <span class="token function">add</span><span class="token punctuation">(</span>E e<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">final</span> ReentrantLock lock <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>lock<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 加锁，保证线程安全</span>    lock<span class="token punctuation">.</span><span class="token function">lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">try</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 获取旧的数组</span>        Object<span class="token punctuation">[</span><span class="token punctuation">]</span> elements <span class="token operator">=</span> <span class="token function">getArray</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">int</span> len <span class="token operator">=</span> elements<span class="token punctuation">.</span>length<span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 【拷贝新的数组（这里是比较耗时的操作，但不影响其它读线程）】</span>        Object<span class="token punctuation">[</span><span class="token punctuation">]</span> newElements <span class="token operator">=</span> Arrays<span class="token punctuation">.</span><span class="token function">copyOf</span><span class="token punctuation">(</span>elements<span class="token punctuation">,</span> len <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 添加新元素</span>        newElements<span class="token punctuation">[</span>len<span class="token punctuation">]</span> <span class="token operator">=</span> e<span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 替换旧的数组，【这个操作以后，其他线程获取数组就是获取的新数组了】</span>        <span class="token function">setArray</span><span class="token punctuation">(</span>newElements<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>        lock<span class="token punctuation">.</span><span class="token function">unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>读操作：不加锁，<strong>在原数组上操作</strong></p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> E <span class="token function">get</span><span class="token punctuation">(</span><span class="token keyword">int</span> index<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">return</span> <span class="token function">get</span><span class="token punctuation">(</span><span class="token function">getArray</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> index<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token keyword">private</span> E <span class="token function">get</span><span class="token punctuation">(</span>Object<span class="token punctuation">[</span><span class="token punctuation">]</span> a<span class="token punctuation">,</span> <span class="token keyword">int</span> index<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">return</span> <span class="token punctuation">(</span>E<span class="token punctuation">)</span> a<span class="token punctuation">[</span>index<span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>适合读多写少的应用场景</p></li><li><p>迭代器：CopyOnWriteArrayList 在返回迭代器时，<strong>创建一个内部数组当前的快照（引用）</strong>，即使其他线程替换了原始数组，迭代器遍历的快照依然引用的是创建快照时的数组，所以这种实现方式也存在一定的数据延迟性，对其他线程并行添加的数据不可见</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> Iterator<span class="token operator">&lt;</span>E<span class="token operator">></span> <span class="token function">iterator</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 获取到数组引用，整个遍历的过程该数组都不会变，一直引用的都是老数组，</span>    <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">COWIterator</span><span class="token operator">&lt;</span>E<span class="token operator">></span><span class="token punctuation">(</span><span class="token function">getArray</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">// 迭代器会创建一个底层array的快照，故主类的修改不影响该快照</span><span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">class</span> <span class="token class-name">COWIterator</span><span class="token operator">&lt;</span>E<span class="token operator">></span> <span class="token keyword">implements</span> <span class="token class-name">ListIterator</span><span class="token operator">&lt;</span>E<span class="token operator">></span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 内部数组快照</span>    <span class="token keyword">private</span> <span class="token keyword">final</span> Object<span class="token punctuation">[</span><span class="token punctuation">]</span> snapshot<span class="token punctuation">;</span>    <span class="token keyword">private</span> <span class="token function">COWIterator</span><span class="token punctuation">(</span>Object<span class="token punctuation">[</span><span class="token punctuation">]</span> elements<span class="token punctuation">,</span> <span class="token keyword">int</span> initialCursor<span class="token punctuation">)</span> <span class="token punctuation">{</span>        cursor <span class="token operator">=</span> initialCursor<span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 数组的引用在迭代过程不会改变</span>        snapshot <span class="token operator">=</span> elements<span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">// 【不支持写操作】，因为是在快照上操作，无法同步回去</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">remove</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">UnsupportedOperationException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span> <span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li></ul><h3 id="弱一致性"><a href="#弱一致性" class="headerlink" title="弱一致性"></a>弱一致性</h3><p>数据一致性就是读到最新更新的数据：</p><ul><li><p>强一致性：当更新操作完成之后，任何多个后续进程或者线程的访问都会返回最新的更新过的值</p></li><li><p>弱一致性：系统并不保证进程或者线程的访问都会返回最新的更新过的值，也不会承诺多久之后可以读到</p></li></ul><img src="https://img.jwt1399.top/img/202301071536087.png" style="zoom:80%;" /><p>Thread-0 读到了脏数据</p><p>不一定弱一致性就不好</p><ul><li>数据库的<strong>事务隔离级别</strong>就是弱一致性的表现</li><li>并发高和一致性是矛盾的，需要权衡</li></ul><h2 id="❸ConcurrentLinkedQueue"><a href="#❸ConcurrentLinkedQueue" class="headerlink" title="❸ConcurrentLinkedQueue"></a>❸ConcurrentLinkedQueue</h2><p>并发编程中，需要用到安全的队列，实现安全队列可以使用 2 种方式：</p><ul><li>加锁，这种实现方式是阻塞队列</li><li>使用循环 CAS 算法实现，这种方式是非阻塞队列</li></ul><p>ConcurrentLinkedQueue 是一个基于链接节点的无界线程安全队列，采用先进先出的规则对节点进行排序，当添加一个元素时，会添加到队列的尾部，当获取一个元素时，会返回队列头部的元素</p><p>补充：ConcurrentLinkedDeque 是双向链表结构的无界并发队列</p><p>ConcurrentLinkedQueue 使用约定：</p><ol><li>不允许 null 入列</li><li>队列中所有未删除的节点的 item 都不能为 null 且都能从 head 节点遍历到</li><li>删除节点是将 item 设置为 null，队列迭代时跳过 item 为 null 节点</li><li>head 节点跟 tail 不一定指向头节点或尾节点，可能<strong>存在滞后性</strong></li></ol><p>ConcurrentLinkedQueue 由 head 节点和 tail 节点组成，每个节点由节点元素和指向下一个节点的引用组成，组成一张链表结构的队列</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">transient</span> <span class="token keyword">volatile</span> Node<span class="token operator">&lt;</span>E<span class="token operator">></span> head<span class="token punctuation">;</span><span class="token keyword">private</span> <span class="token keyword">transient</span> <span class="token keyword">volatile</span> Node<span class="token operator">&lt;</span>E<span class="token operator">></span> tail<span class="token punctuation">;</span><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">Node</span><span class="token operator">&lt;</span>E<span class="token operator">></span> <span class="token punctuation">{</span>    <span class="token keyword">volatile</span> E item<span class="token punctuation">;</span>    <span class="token keyword">volatile</span> Node<span class="token operator">&lt;</span>E<span class="token operator">></span> next<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">//.....</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="❹ConcurrentSkipListMap"><a href="#❹ConcurrentSkipListMap" class="headerlink" title="❹ConcurrentSkipListMap"></a>❹ConcurrentSkipListMap</h2><p>。。。</p><h1 id="Sponsor❤️"><a href="#Sponsor❤️" class="headerlink" title="Sponsor❤️"></a>Sponsor❤️</h1><p>您的支持是我不断前进的动力，如果您感觉本文对您有所帮助的话，可以考虑打赏一下本文，用以维持本博客的运营费用，拒绝白嫖，从你我做起！🥰🥰🥰</p><table>  <tbody>     <tr>         <td style="text-align:center;">支付宝</td>         <td style="text-align:center;">微信</td>     </tr>   <tr>    <td style="text-align:center;" ><img width="200" src="https://jwt1399.top/medias/reward/alipay.png"></td>          <td style="text-align:center;"><img width="200" src="https://jwt1399.top/medias/reward/sponor_wechat.png"></td>       </tr></tbody></table>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;h1 id=&quot;⓪基础&quot;&gt;&lt;a href=&quot;#⓪基础&quot; class=&quot;headerlink&quot; title=&quot;⓪基础&quot;&gt;&lt;/a&gt;⓪基础&lt;/h1&gt;&lt;h2 id=&quot;❶进程-amp-线程&quot;&gt;&lt;a href=&quot;#❶进程-amp-线程&quot; class=&quot;headerlink&quot;</summary>
        
      
    
    
    
    <category term="JavaSE" scheme="https://jwt1399.top/categories/JavaSE/"/>
    
    
    <category term="JUC" scheme="https://jwt1399.top/tags/JUC/"/>
    
  </entry>
  
  <entry>
    <title>Java-JVM</title>
    <link href="https://jwt1399.top/posts/14485.html"/>
    <id>https://jwt1399.top/posts/14485.html</id>
    <published>2022-12-02T13:17:24.000Z</published>
    <updated>2023-01-20T05:34:30.307Z</updated>
    
    <content type="html"><![CDATA[<h1 id="①JVM概述"><a href="#①JVM概述" class="headerlink" title="①JVM概述"></a>①JVM概述</h1><h2 id="❶基本介绍"><a href="#❶基本介绍" class="headerlink" title="❶基本介绍"></a>❶基本介绍</h2><p>JVM：全称 Java Virtual Machine，一个虚拟计算机，Java 程序的运行环境（Java二进制字节码的运行环境）</p><p>特点：</p><ul><li>Java 虚拟机基于<strong>二进制字节码</strong>执行，由一套字节码指令集、一组寄存器、一个栈、一个垃圾回收堆、一个方法区等组成</li><li>JVM 屏蔽了与操作系统平台相关的信息，从而能够让 Java 程序只需要生成能够在 JVM 上运行的字节码文件，通过该机制实现的<strong>跨平台性</strong>。即一次编译，处处执行</li><li>自动的内存管理，垃圾回收机制</li></ul><p>JVM 结构：</p><p><img src="https://img.jwt1399.top/img/202212031705537.png"></p><img src="https://img.jwt1399.top/img/202212202200355.png" alt="image-20221220215946131" style="zoom: 50%;" /><p>Java 代码执行流程：<code>Java 程序(.java) --（编译）--&gt; 字节码文件(.class)--（解释执行/JIT）--&gt; 操作系统（Win，Linux）</code></p><table><thead><tr><th><img src="https://img.jwt1399.top/img/202212031714323.png"></th><th><img src="https://img.jwt1399.top/img/202212031714094"></th></tr></thead></table><p>JVM、JRE、JDK 对比：</p><ul><li>JDK(Java SE Development Kit)：Java 标准开发包，提供了编译、运行 Java 程序所需的各种工具和资源</li><li>JRE( Java Runtime Environment)：Java 运行环境，用于解释执行 Java 的字节码文件</li></ul><img src="https://img.jwt1399.top/img/202212031706253.png" /><hr><h2 id="❷架构模型"><a href="#❷架构模型" class="headerlink" title="❷架构模型"></a>❷架构模型</h2><p>Java 编译器输入的指令流是一种基于栈的指令集架构。因为跨平台的设计，Java 的指令都是根据栈来设计的，不同平台 CPU 架构不同，所以不能设计为基于寄存器架构</p><ul><li>基于栈式架构的特点：<ul><li>设计和实现简单，适用于资源受限的系统</li><li>使用零地址指令方式分配，执行过程依赖操作栈，指令集更小，编译器容易实现<ul><li>零地址指令：机器指令的一种，是指令系统中的一种不设地址字段的指令，只有操作码而没有地址码。这种指令有两种情况：一是无需操作数，另一种是操作数为默认的（隐含的），默认为操作数在寄存器（ACC）中，指令可直接访问寄存器</li><li>一地址指令：一个操作码对应一个地址码，通过地址码寻找操作数</li></ul></li><li>不需要硬件的支持，可移植性更好，更好实现跨平台</li></ul></li><li>基于寄存器架构的特点：<ul><li>需要硬件的支持，可移植性差</li><li>性能更好，执行更高效，寄存器比内存快</li><li>以一地址指令、二地址指令、三地址指令为主，而基于栈式架构的指令集却是以零地址指令为主</li></ul></li></ul><hr><h2 id="❸生命周期"><a href="#❸生命周期" class="headerlink" title="❸生命周期"></a>❸生命周期</h2><p>JVM 的生命周期分为三个阶段，分别为：启动、运行、死亡</p><ul><li><p><strong>启动</strong>：当启动一个 Java 程序时，通过引导类加载器（bootstrap class loader）创建一个初始类（initial class），对于拥有 main 函数的类就是 JVM 实例运行的起点</p></li><li><p><strong>运行</strong>：</p><ul><li><p>main() 方法是一个程序的初始起点，任何线程均可由在此处启动</p></li><li><p>在 JVM 内部有两种线程类型，分别为：用户线程和守护线程，<strong>JVM 使用的是守护线程，main() 和其他线程使用的是用户线程</strong>，守护线程会随着用户线程的结束而结束</p></li><li><p>执行一个 Java 程序时，真真正正在执行的是一个 <strong>Java 虚拟机的进程</strong></p></li><li><p>JVM 有两种运行模式 Server 与 Client，两种模式的区别在于：Client 模式启动速度较快，Server 模式启动较慢；但是启动进入稳定期长期运行之后 Server 模式的程序运行速度比 Client 要快很多</p><p>Server 模式启动的 JVM 采用的是重量级的虚拟机，对程序采用了更多的优化；Client 模式启动的 JVM 采用的是轻量级的虚拟机</p></li></ul></li><li><p><strong>死亡</strong>：</p><ul><li>当程序中的用户线程都中止，JVM 才会退出</li><li>程序正常执行结束、程序异常或错误而异常终止、操作系统错误导致终止</li><li>线程调用 Runtime 类 halt 方法或 System 类 exit 方法，并且 Java 安全管理器允许这次 exit 或 halt 操作</li></ul></li></ul><h1 id="②内存结构"><a href="#②内存结构" class="headerlink" title="②内存结构"></a>②内存结构</h1><h2 id="❶JVM内存"><a href="#❶JVM内存" class="headerlink" title="❶JVM内存"></a>❶JVM内存</h2><h3 id="0-内存概述"><a href="#0-内存概述" class="headerlink" title="0.内存概述"></a>0.内存概述</h3><p><strong>内存结构</strong>是 JVM 中非常重要的一部分，是非常重要的系统资源，是硬盘和 CPU 的桥梁，承载着操作系统和应用程序的实时运行，又叫<strong>运行时数据区</strong>。</p><p>JVM 内存结构规定了 Java 在运行过程中内存申请、分配、管理的策略，保证了 JVM 的高效稳定运行</p><ul><li>Java1.8 以前的内存结构图：</li></ul><p><img src="https://img.jwt1399.top/img/202212051655611.png"></p><ul><li>Java1.8 之后的内存结果图：</li></ul><p><img src="https://img.jwt1399.top/img/202212042053847.png"></p><h3 id="1-程序计数器"><a href="#1-程序计数器" class="headerlink" title="1.程序计数器"></a>1.程序计数器</h3><blockquote><p>Program Counter Register 程序计数器（寄存器）</p></blockquote><p>作用：内部保存字节码的行号，用于记录正在执行的字节码指令地址（如果正在执行的是本地方法则为空）</p><p>原理：</p><ul><li>JVM 对于多线程是通过线程轮流切换并且分配线程执行时间，一个处理器只会处理执行一个线程</li><li>切换线程需要从程序计数器中来回切换到当前的线程上一次执行的行号</li></ul><p>特点：</p><ul><li>是线程私有的</li><li><strong>不会存在内存溢出</strong>，是 JVM 规范中唯一一个不出现 OOM 的区域，所以这个空间不会进行 GC</li></ul><p><img src="https://img.jwt1399.top/img/202212051416292.png"></p><h3 id="2-虚拟机栈"><a href="#2-虚拟机栈" class="headerlink" title="2.虚拟机栈"></a>2.虚拟机栈</h3><blockquote><p>Java 虚拟机栈：Java Virtual Machine Stacks，<strong>每个线程</strong>运行时所需要的<strong>内存</strong></p><p>异常：<code>java.lang.StackOverflowError</code></p></blockquote><p><img src="https://img.jwt1399.top/img/202212042241637.png"></p><ul><li><p>每个方法被执行时，都会在虚拟机栈中创建一个栈帧 stack frame（<strong>一个方法一个栈帧</strong>）</p></li><li><p>Java 虚拟机规范允许 <strong>Java 栈的大小是动态的或者是固定不变的</strong></p></li><li><p>虚拟机栈是<strong>每个线程私有的</strong>，每个线程只能有一个活动栈帧，对应方法调用到执行完成的整个过程</p></li><li><p><strong>每个栈由多个栈帧（Frame）组成</strong>，对应着每次方法调用时所占用的内存，每个栈帧中存储着：</p><ul><li>局部变量表：存储方法里的 Java 基本数据类型以及对象的引用（reference 类型）</li><li>动态链接：也叫指向运行时常量池的方法引用</li><li>方法返回地址：方法正常退出或者异常退出的定义</li><li>操作数栈或表达式栈和其他一些附加信息</li></ul></li></ul><p>设置栈内存大小：<code>-Xss size</code>   <code>-Xss 1024k</code>（在 VM options 中设置）</p><ul><li>在 JDK 1.4 中默认为 256K，而在 JDK 1.5+ 默认为 1M</li></ul><p>虚拟机栈特点：</p><ul><li><p>栈内存<strong>不需要进行GC</strong>，方法开始执行的时候会进栈，方法调用后自动弹栈，相当于清空了数据</p></li><li><p>栈内存分配越大，可用的线程数越少（内存越大，每个线程拥有的内存越大）</p></li><li><p>方法内的局部变量是否<strong>线程安全</strong>：</p><ul><li>如果方法内局部变量没有逃离方法的作用访问，它是线程安全的（逃逸分析）</li><li>如果是局部变量引用了对象，并逃离方法的作用范围，需要考虑线程安全</li></ul></li></ul><p>异常：</p><ul><li>栈帧过多导致栈内存溢出 （超过了栈的容量），会抛出 OutOfMemoryError 异常</li><li>当线程请求的栈深度超过虚拟机允许的最大深度时，会抛出 StackOverflowError 异常</li></ul><p>线程运行诊断：</p><ul><li>定位：<ul><li>jps 定位进程 ID</li><li>top定位哪个进程对cpu的占用过高</li><li>ps  -eo pid,%cpu | grep 进程id （用ps命令进一步定位是哪个线程引起的cpu占用过高）</li></ul></li><li>jstack 进程 ID：用于打印出给定的 Java 进程 ID 或 core file 或远程调试服务的 Java 堆栈信息</li></ul><h3 id="3-本地方法栈"><a href="#3-本地方法栈" class="headerlink" title="3.本地方法栈"></a>3.本地方法栈</h3><blockquote><p>本地方法栈：Native Method Stacks， 为虚拟机执行本地方法（native方法）时提供服务的</p></blockquote><ul><li>本地方法一般是由其他语言编写（C&#x2F;C++），并且被编译为基于本机硬件和操作系统的程序</li><li>与虚拟机栈类似，不需要进行 GC，也是线程私有的，有 StackOverFlowError 和 OutOfMemoryError 异常</li><li>本地方法用 <code>native</code> 关键字修饰，例如：Object类中的wait()、clone()、hashCode等</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token keyword">native</span> <span class="token keyword">void</span> <span class="token function">wait</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">public</span> <span class="token keyword">native</span> <span class="token keyword">int</span> <span class="token function">hashCode</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">protected</span> <span class="token keyword">native</span> Object <span class="token function">clone</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>当某个线程调用一个本地方法时，就进入了不再受虚拟机限制的世界，和虚拟机拥有同样的权限</p><ul><li>本地方法可以通过本地方法接口（JNI：Java Native Interface）来<strong>访问虚拟机内部的运行时数据区</strong></li><li>直接从本地内存的堆中分配任意数量的内存</li><li>可以直接使用本地处理器中的寄存器</li></ul><h3 id="4-堆"><a href="#4-堆" class="headerlink" title="4.堆"></a>4.堆</h3><h4 id="堆概述"><a href="#堆概述" class="headerlink" title="堆概述"></a>堆概述</h4><blockquote><p>堆：Heap ，是 JVM 内存中最大的一块，由所有<strong>线程共享</strong>，由<strong>垃圾回收器</strong>管理的主要区域，堆中对象大部分都需要考虑<strong>线程安全</strong>的问题</p><p>异常：java.lang.OutOfMemoryError：java heap space</p></blockquote><p><img src="https://img.jwt1399.top/img/202212051449758.png"></p><p>存放哪些资源：</p><ul><li><strong>对象实例</strong>：new 创建的对象，类初始化生成的对象，<strong>基本数据类型的数组也是对象实例</strong>(new 创建)</li><li><strong>字符串常量池</strong> StringTable&#x2F;String Pool： JVM 为了提升性能和减少内存消耗针对字符串（String 类）专门开辟的一块区域，主要目的是为了避免字符串的重复创建。<ul><li>字符串常量池原本存放于方法区，JDK7 开始放置于堆中</li><li>字符串常量池<strong>存储的是 String 对象的直接引用或者对象</strong>，是一张 <strong>stringTable</strong></li><li>在jdk 7之后，原先位于方法区里的字符串常量池已被移动到了java堆中。</li></ul></li><li><strong>静态变量</strong>：静态变量是有 static 修饰的变量，JDK8 时从方法区迁移至堆中</li><li><strong>线程分配缓冲区</strong> Thread Local Allocation Buffer：线程私有但不影响堆的共性，可以提升对象分配的效率</li></ul><p>内存溢出：new 出对象，循环添加字符数据，当堆中没有内存空间可分配给实例，也无法再扩展时，就会抛出 OutOfMemoryError 异常</p><p>设置堆内存指令：<code>-Xmx Size</code></p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">/** * 演示堆内存溢出 java.lang.OutOfMemoryError: Java heap space * -Xmx8m */</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Demo1_5</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>        <span class="token keyword">try</span> <span class="token punctuation">{</span>            List<span class="token operator">&lt;</span>String<span class="token operator">></span> list <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ArrayList</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            String a <span class="token operator">=</span> <span class="token string">"hello"</span><span class="token punctuation">;</span>            <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                list<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>a<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// hello, hellohello, hellohellohellohello ...</span>                a <span class="token operator">=</span> a <span class="token operator">+</span> a<span class="token punctuation">;</span>  <span class="token comment" spellcheck="true">// hellohellohellohello</span>                i<span class="token operator">++</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Throwable</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span>            e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="堆内存诊断工具"><a href="#堆内存诊断工具" class="headerlink" title="堆内存诊断工具"></a>堆内存诊断工具</h4><ol><li>jps：查看当前系统中有哪些 Java 进程</li><li>jmap：查看某一时刻堆内存占用情况  <code>jhsdb jmap --heap --pid 进程id</code></li><li>jconsole：图形界面的，多功能的监测工具，可以连续监测</li><li>jvisualvm：图形界面的，多功能的监测工具，可以连续监测</li></ol><h4 id="Java7-x2F-8堆变化"><a href="#Java7-x2F-8堆变化" class="headerlink" title="Java7&#x2F;8堆变化"></a>Java7&#x2F;8堆变化</h4><img src="https://img.jwt1399.top/img/202212051751417.png" style="zoom:50%;" /><p>在 Java7 中堆内会存在<strong>年轻代、老年代和方法区（永久代）</strong>，Java8 永久代被<strong>元空间</strong>代替了</p><ul><li>Young 区被划分为三部分，Eden 区和两个大小严格相同的 Survivor 区。Survivor 区某一时刻只有其中一个是被使用的，另外一个留做垃圾回收时复制对象。在 Eden 区变满的时候，GC 就会将存活的对象移到空闲的 Survivor 区间中，根据 JVM 的策略，在经过几次垃圾回收后，仍然存活于 Survivor 的对象将被移动到 Tenured 区间</li><li>Old 区主要保存生命周期长的对象，一般是一些老的对象，当一些对象在 Young 复制转移一定的次数以后，对象就会被转移到 Tenured 区</li><li>Perm 代主要保存 Class、ClassLoader、静态变量、常量、编译后的代码，在 Java7 中堆内方法区会受到 GC 的管理</li></ul><p>分代原因：不同对象的生命周期不同，70%-99% 的对象都是临时对象，优化 GC 性能</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 返回Java虚拟机中的堆内存总量</span>    <span class="token keyword">long</span> initialMemory <span class="token operator">=</span> Runtime<span class="token punctuation">.</span><span class="token function">getRuntime</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">totalMemory</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">/</span> <span class="token number">1024</span> <span class="token operator">/</span> <span class="token number">1024</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 返回Java虚拟机使用的最大堆内存量</span>    <span class="token keyword">long</span> maxMemory <span class="token operator">=</span> Runtime<span class="token punctuation">.</span><span class="token function">getRuntime</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">maxMemory</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">/</span> <span class="token number">1024</span> <span class="token operator">/</span> <span class="token number">1024</span><span class="token punctuation">;</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"-Xms : "</span> <span class="token operator">+</span> initialMemory <span class="token operator">+</span> <span class="token string">"M"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//-Xms : 245M</span>    System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"-Xmx : "</span> <span class="token operator">+</span> maxMemory <span class="token operator">+</span> <span class="token string">"M"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//-Xmx : 3641M</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="StringTable-x2F-String-Pool"><a href="#StringTable-x2F-String-Pool" class="headerlink" title="StringTable&#x2F;String Pool"></a>StringTable&#x2F;String Pool</h4><blockquote><p>字符串常量池（String Pool &#x2F; StringTable &#x2F; 串池）存储的是 String 对象的直接引用或者对象，即保存着所有字符串字面量（literal strings），这些字面量在编译时期就确定，字符串常量池类似于 Java 系统级别提供的<strong>缓存</strong>，存放对象和引用</p><p>StringTable，类似 HashTable 结构，通过 <code>-XX:StringTableSize</code> 设置大小，JDK 1.8 中默认 60013</p></blockquote><h5 id="字符串拼接"><a href="#字符串拼接" class="headerlink" title="字符串拼接"></a>字符串拼接</h5><ul><li>常量池中的字符串仅是符号，第一次使用时才变为对象(加入到运行时常量池),可以避免重复创建字符串对象</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// 字符串常量池(StringTable): [ "a", "b" ,"ab" ]  hashtable 结构，不能扩容</span><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>  String s1 <span class="token operator">=</span> <span class="token string">"a"</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 懒惰的</span>  String s2 <span class="token operator">=</span> <span class="token string">"b"</span><span class="token punctuation">;</span>  String s3 <span class="token operator">=</span> <span class="token string">"ab"</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>字节码：Java 反编译指令<code>javap -v 文件名.class</code></p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">//常量池</span><span class="token comment" spellcheck="true">// 常量池中的信息，都会被加载到运行时常量池中， </span><span class="token comment" spellcheck="true">// 这时 a b ab 都是常量池中的符号，还没有变为 java 字符串对象，是懒惰的</span>Constant pool<span class="token operator">:</span>    #<span class="token number">1</span> <span class="token operator">=</span> Methodref          #<span class="token number">12</span><span class="token punctuation">.</span>#<span class="token number">36</span>        <span class="token comment" spellcheck="true">// java/lang/Object."&lt;init>":()V</span>   #<span class="token number">2</span> <span class="token operator">=</span> String             #<span class="token number">37</span>            <span class="token comment" spellcheck="true">// a</span>   #<span class="token number">3</span> <span class="token operator">=</span> String             #<span class="token number">38</span>            <span class="token comment" spellcheck="true">// b</span>   #<span class="token number">4</span> <span class="token operator">=</span> String             #<span class="token number">39</span>            <span class="token comment" spellcheck="true">// ab</span><span class="token comment" spellcheck="true">//运行代码       </span> <span class="token number">0</span><span class="token operator">:</span> ldc           #<span class="token number">2</span>                  <span class="token comment" spellcheck="true">// String a</span> <span class="token number">2</span><span class="token operator">:</span> astore_1 <span class="token comment" spellcheck="true">//存入局部变量表slot 1号位</span> <span class="token number">3</span><span class="token operator">:</span> ldc           #<span class="token number">3</span>                  <span class="token comment" spellcheck="true">// String b</span> <span class="token number">5</span><span class="token operator">:</span> astore_2 <span class="token number">6</span><span class="token operator">:</span> ldc           #<span class="token number">4</span>                  <span class="token comment" spellcheck="true">// String ab</span> <span class="token number">8</span><span class="token operator">:</span> astore_3   <span class="token comment" spellcheck="true">// ldc #2 会把 a 符号变为 "a" 字符串对象，StringTable: ["a"] </span><span class="token comment" spellcheck="true">// ldc #3 会把 b 符号变为 "b" 字符串对象，StringTable: ["a", "b"] </span><span class="token comment" spellcheck="true">// ldc #4 会把 ab 符号变为 "ab" 字符串对象，StringTable: ["a", "b" ,"ab"]    </span>          <span class="token comment" spellcheck="true">//局部变量表(栈)  </span>LocalVariableTable<span class="token operator">:</span>          Start  Length  Slot  Name   Signature            <span class="token number">0</span>      <span class="token number">51</span>     <span class="token number">0</span>  args   <span class="token punctuation">[</span>Ljava<span class="token operator">/</span>lang<span class="token operator">/</span>String<span class="token punctuation">;</span>            <span class="token number">3</span>      <span class="token number">48</span>     <span class="token number">1</span>    s1   Ljava<span class="token operator">/</span>lang<span class="token operator">/</span>String<span class="token punctuation">;</span>            <span class="token number">6</span>      <span class="token number">45</span>     <span class="token number">2</span>    s2   Ljava<span class="token operator">/</span>lang<span class="token operator">/</span>String<span class="token punctuation">;</span>            <span class="token number">9</span>      <span class="token number">42</span>     <span class="token number">3</span>    s3   Ljava<span class="token operator">/</span>lang<span class="token operator">/</span>String<span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>字符串<strong>变量</strong>的拼接的原理是 StringBuilder#append，append 方法比字符串拼接效率高（JDK 1.8）</li><li>字符串<strong>常量</strong>拼接的原理是编译期优化，拼接结果放入常量池</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// 字符串常量池(StringTable): [ "a", "b" ,"ab" ]  hashtable 结构，不能扩容</span><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>  String s1 <span class="token operator">=</span> <span class="token string">"a"</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 懒惰的</span>  String s2 <span class="token operator">=</span> <span class="token string">"b"</span><span class="token punctuation">;</span>  String s3 <span class="token operator">=</span> <span class="token string">"ab"</span><span class="token punctuation">;</span>  <span class="token comment" spellcheck="true">// new StringBuilder().append("a").append("b").toString() -->  new String("ab")  堆中</span>  String s4 <span class="token operator">=</span> s1 <span class="token operator">+</span> s2<span class="token punctuation">;</span>   <span class="token comment" spellcheck="true">//字符串变量   // 返回的是堆内地址</span>  <span class="token comment" spellcheck="true">// javac 在编译期间的优化，结果已经在编译期确定为ab</span>  String s5 <span class="token operator">=</span> <span class="token string">"a"</span> <span class="token operator">+</span> <span class="token string">"b"</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//字符串常量</span>    System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>s3 <span class="token operator">==</span> s4<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// F</span>  System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>s3 <span class="token operator">==</span> s5<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// T</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h5 id="intern"><a href="#intern" class="headerlink" title="intern()"></a>intern()</h5><p>JDK 1.8：将这个字符串对象尝试放入串池，如果 String Pool 中：</p><ul><li>存在一个字符串和该字符串值相等，就会返回 String Pool 中字符串的引用（需要变量接收）</li><li>不存在，会把对象的<strong>引用地址</strong>复制一份放入串池，并返回串池中的引用地址，前提是堆内存有该对象，因为 Pool 在堆中，为了节省内存不再创建新对象</li></ul><p>JDK 1.6：将这个字符串对象尝试放入串池，如果 String Pool 中：</p><ul><li>如果有就不放入，返回已有的串池中的对象的引用；</li><li>如果没有会把此对象复制一份，放入串池，把串池中的对象返回</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// StringTable: ["ab", "a", "b"]</span><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>  String x <span class="token operator">=</span> <span class="token string">"ab"</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// StringTable: ["ab"]</span>    <span class="token comment" spellcheck="true">// 堆  new String("a")   new String("b")  new StringBuilder()  new String("ab")</span>  <span class="token comment" spellcheck="true">// StringTable: ["ab", "a", "b"]</span>  String s <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">String</span><span class="token punctuation">(</span><span class="token string">"a"</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token keyword">new</span> <span class="token class-name">String</span><span class="token punctuation">(</span><span class="token string">"b"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>   <span class="token comment" spellcheck="true">// s只存在堆中，不存在StringTable</span>  <span class="token comment" spellcheck="true">// 将这个字符串对象尝试放入串池，如果有则并不会放入，如果没有则放入串池， 会把串池中的对象返回</span>  String s2 <span class="token operator">=</span> s<span class="token punctuation">.</span><span class="token function">intern</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>   System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>s <span class="token operator">==</span> x<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment" spellcheck="true">// F</span>  System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>s2 <span class="token operator">==</span> x<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// T</span>  <span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>&#x3D;&#x3D; 比较基本数据类型：比较的是具体的值</li><li>&#x3D;&#x3D; 比较引用数据类型：比较的是对象地址值</li></ul><p>结论：</p><pre class="line-numbers language-java"><code class="language-java">String s1 <span class="token operator">=</span> <span class="token string">"ab"</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// ab仅放入串池  StringTable: ["ab"]</span>String s2 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">String</span><span class="token punctuation">(</span><span class="token string">"a"</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token keyword">new</span> <span class="token class-name">String</span><span class="token punctuation">(</span><span class="token string">"b"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// ab仅放入堆  StringTable: ["a","b"]</span>String s <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">String</span><span class="token punctuation">(</span><span class="token string">"ab"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// ab串池和堆都存在 StringTable: ["ab"]</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><hr><h5 id="常见问题"><a href="#常见问题" class="headerlink" title="常见问题"></a>常见问题</h5><p>问题一：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>    String s <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">String</span><span class="token punctuation">(</span><span class="token string">"a"</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token keyword">new</span> <span class="token class-name">String</span><span class="token punctuation">(</span><span class="token string">"b"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//new String("ab")</span>    <span class="token comment" spellcheck="true">//在上一行代码执行完以后，字符串常量池中并没有"ab"</span>    String s2 <span class="token operator">=</span> s<span class="token punctuation">.</span><span class="token function">intern</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">//jdk6：串池中创建一个字符串"ab"，把此对象复制一份</span>    <span class="token comment" spellcheck="true">//jdk8：串池中没有创建字符串"ab",而是创建一个引用指向 new String("ab")，将此引用返回</span>    System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>s2 <span class="token operator">==</span> <span class="token string">"ab"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//jdk6:true  jdk8:true</span>    System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>s <span class="token operator">==</span> <span class="token string">"ab"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//jdk6:false  jdk8:true</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>问题二：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>    String str1 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">StringBuilder</span><span class="token punctuation">(</span><span class="token string">"58"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span><span class="token string">"tongcheng"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>str1 <span class="token operator">==</span> str1<span class="token punctuation">.</span><span class="token function">intern</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//true，字符串池中不存在，把堆中的引用复制一份放入串池</span>    String str2 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">StringBuilder</span><span class="token punctuation">(</span><span class="token string">"ja"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span><span class="token string">"va"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>str2 <span class="token operator">==</span> str2<span class="token punctuation">.</span><span class="token function">intern</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//false，字符串池中存在，直接返回已经存在的引用</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>原因：</p><ul><li><p>System 类当调用 Version 的静态方法，导致 Version 初始化：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">initializeSystemClass</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    sun<span class="token punctuation">.</span>misc<span class="token punctuation">.</span>Version<span class="token punctuation">.</span><span class="token function">init</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre></li><li><p>Version 类初始化时需要对静态常量字段初始化，被 launcher_name 静态常量字段所引用的 <code>&quot;java&quot;</code> 字符串字面量就被放入的字符串常量池：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">package</span> sun<span class="token punctuation">.</span>misc<span class="token punctuation">;</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Version</span> <span class="token punctuation">{</span>    <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> String launcher_name <span class="token operator">=</span> <span class="token string">"java"</span><span class="token punctuation">;</span>    <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> String java_version <span class="token operator">=</span> <span class="token string">"1.8.0_221"</span><span class="token punctuation">;</span>    <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> String java_runtime_name <span class="token operator">=</span> <span class="token string">"Java(TM) SE Runtime Environment"</span><span class="token punctuation">;</span>    <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> String java_profile_name <span class="token operator">=</span> <span class="token string">""</span><span class="token punctuation">;</span>    <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> String java_runtime_version <span class="token operator">=</span> <span class="token string">"1.8.0_221-b11"</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">//...</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li></ul><h5 id="内存位置"><a href="#内存位置" class="headerlink" title="内存位置"></a>内存位置</h5><p>Java 7 之前，String Pool 被放在运行时常量池中，属于永久代；Java 7 以后，String Pool 被移到堆中，这是因为永久代的空间有限，在大量使用字符串的场景下会导致 OutOfMemoryError 错误</p><p>演示 StringTable 位置：</p><ul><li><p><code>-Xmx10m</code> 设置堆内存 10m</p></li><li><p>在 JDK8 下设置： <code>-Xmx10m -XX:-UseGCOverheadLimit</code></p></li><li><p>在 JDK6 下设置： <code>-XX:MaxPermSize=10m</code></p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token keyword">throws</span> InterruptedException <span class="token punctuation">{</span>    List<span class="token operator">&lt;</span>String<span class="token operator">></span> list <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ArrayList</span><span class="token operator">&lt;</span>String<span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>    <span class="token keyword">try</span> <span class="token punctuation">{</span>        <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> j <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> j <span class="token operator">&lt;</span> <span class="token number">260000</span><span class="token punctuation">;</span> j<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            list<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>String<span class="token punctuation">.</span><span class="token function">valueOf</span><span class="token punctuation">(</span>j<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">intern</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            i<span class="token operator">++</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Throwable</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span>        e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li></ul><p><img src="https://img.jwt1399.top/img/202212061628844.png"></p><hr><h5 id="优化常量池"><a href="#优化常量池" class="headerlink" title="优化常量池"></a>优化常量池</h5><p>两种方式：</p><ul><li><p>调整 <code>-XX:StringTableSize=桶个数</code>，数量越少，性能越差</p></li><li><p>intern 将字符串对象放入常量池，通过复用字符串的引用，减少内存占用</p></li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">/** * 演示 intern 减少内存占用 * -XX:StringTableSize=200000 -XX:+PrintStringTableStatistics * -Xsx500m -Xmx500m -XX:+PrintStringTableStatistics -XX:StringTableSize=200000 */</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Demo1_25</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token keyword">throws</span> IOException <span class="token punctuation">{</span>        List<span class="token operator">&lt;</span>String<span class="token operator">></span> address <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ArrayList</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        System<span class="token punctuation">.</span>in<span class="token punctuation">.</span><span class="token function">read</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> <span class="token number">10</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">//很多数据</span>            <span class="token keyword">try</span> <span class="token punctuation">(</span>BufferedReader reader <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">BufferedReader</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">InputStreamReader</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">FileInputStream</span><span class="token punctuation">(</span><span class="token string">"linux.words"</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token string">"utf-8"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                String line <span class="token operator">=</span> null<span class="token punctuation">;</span>                <span class="token keyword">long</span> start <span class="token operator">=</span> System<span class="token punctuation">.</span><span class="token function">nanoTime</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                    line <span class="token operator">=</span> reader<span class="token punctuation">.</span><span class="token function">readLine</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                    <span class="token keyword">if</span><span class="token punctuation">(</span>line <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>                        <span class="token keyword">break</span><span class="token punctuation">;</span>                    <span class="token punctuation">}</span>                    address<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>line<span class="token punctuation">.</span><span class="token function">intern</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token punctuation">}</span>                System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"cost:"</span> <span class="token operator">+</span><span class="token punctuation">(</span>System<span class="token punctuation">.</span><span class="token function">nanoTime</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-</span>start<span class="token punctuation">)</span><span class="token operator">/</span><span class="token number">1000000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span>        System<span class="token punctuation">.</span>in<span class="token punctuation">.</span><span class="token function">read</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h5 id="不可变好处"><a href="#不可变好处" class="headerlink" title="不可变好处"></a>不可变好处</h5><ul><li>可以缓存 hash 值，例如 String 用做 HashMap 的 key，不可变的特性可以使得 hash 值也不可变，只要进行一次计算</li><li>String Pool 的需要，如果一个 String 对象已经被创建过了，就会从 String Pool 中取得引用，只有 String 是不可变的，才可能使用 String Pool</li><li>安全性，String 经常作为参数，String 不可变性可以保证参数不可变。例如在作为网络连接参数的情况下如果 String 是可变的，那么在网络连接过程中，String 被改变，改变 String 的那一方以为现在连接的是其它主机，而实际情况却不一定是</li><li>String 不可变性天生具备线程安全，可以在多个线程中安全地使用</li><li>防止子类继承，破坏 String 的 API 的使用</li></ul><h3 id="5-方法区"><a href="#5-方法区" class="headerlink" title="5.方法区"></a>5.方法区</h3><blockquote><p>方法区 Method Area：是各个<strong>线程共享</strong>的内存区域，用于存储已被虚拟机加载的类信息、常量、即时编译器编译后的代码等数据，虽然 Java 虚拟机规范把方法区描述为堆的一个逻辑部分，但是也叫 Non-Heap（非堆）</p><p>Java 1.8 以前：方法区由<strong>永久代</strong>实现</p><p>Java 1.8 之后：方法区由<strong>元空间</strong>实现</p><p>异常：java.lang.OutOfMemoryError：Metaspace</p></blockquote><img src="https://img.jwt1399.top/img/202212051651947.png" style="zoom:50%;" /><p>方法区构成：</p><ul><li><p><strong>类元信息</strong>：在类编译期间放入方法区，存放了类的基本信息，包括类的方法、参数、接口以及常量池表</p></li><li><p><strong>常量池表</strong>（Constant Pool Table）是 Class 文件的一部分，存储了<strong>类在编译期间生成的字面量、符号引用</strong>，JVM 为每个已加载的类维护一个常量池</p><ul><li><p>字面量：基本数据类型、字符串类型常量、声明为 final 的常量值等</p><ul><li><p>java 代码在编译过程中是无法构建引用的，字面量就是在编译时对于数据的一种表示</p></li><li><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">final</span> <span class="token keyword">int</span> a <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//这个1便是字面量</span>String b <span class="token operator">=</span> <span class="token string">"jwt"</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//jwt便是字面量</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre></li></ul></li><li><p>符号引用：类、字段、方法、接口等的符号引用</p><ul><li>在编译过程中并不知道每个类的地址，因为可能这个类还没有加载，如果在一个类中引用了另一个类，无法知道它的内存地址，只能用它的类名作为符号引用，在类加载完后用这个符号引用去获取内存地址</li></ul></li></ul></li><li><p><strong>运行时常量池</strong></p><ul><li><p>常量池（编译器生成的字面量和符号引用）中的数据会在类加载的加载阶段放入运行时常量池</p></li><li><p>类在解析阶段将这些符号引用替换成直接引用</p></li><li><p>除了在编译期生成的常量，还允许动态生成，例如 String 类的 intern()</p></li></ul></li></ul><p>特点：</p><ul><li><p>方法区是一个 JVM 规范，<strong>永久代与元空间都是其一种实现方式</strong></p></li><li><p>方法区的大小不必是固定的，可以动态扩展，加载的类太多，可能导致永久代内存溢出 (OutOfMemoryError)</p></li><li><p>方法区的 GC：针对常量池的回收及对类型的卸载，比较难实现</p></li><li><p>为了<strong>避免方法区出现 OOM</strong>，在 JDK8 中将堆内的方法区（永久代）移动到了本地内存上，重新开辟了一块空间，叫做元空间，元空间存储类的元信息，<strong>静态变量和字符串常量池等放入堆中</strong></p></li><li><p>Java 1.8 以前：永久代内存溢出 java.lang.OutOfMemoryError: PermGen space</p></li><li><p>Java 1.8 之后：元空间内存溢出 java.lang.OutOfMemoryError: Metaspace</p></li></ul><h2 id="❷本地内存"><a href="#❷本地内存" class="headerlink" title="❷本地内存"></a>❷本地内存</h2><img src="https://img.jwt1399.top/img/202212051652808.png" style="zoom:50%;" /><p>JVM内存：Java 虚拟机在执行的时候会把管理的内存分配成不同的区域，受虚拟机内存大小的参数控制，当大小超过参数设置的大小时就会报 OOM</p><p>本地内存：又叫做<strong>堆外内存</strong>，线程共享的区域，本地内存这块区域是不会受到 JVM 的控制的，不会发生 GC；因此对于整个 Java 的执行效率是提升非常大，但是如果内存的占用超出物理内存的大小，同样也会报 OOM</p><h3 id="1-方法区-x2F-元空间"><a href="#1-方法区-x2F-元空间" class="headerlink" title="1.方法区&#x2F;元空间"></a>1.方法区&#x2F;元空间</h3><p>Java8 开始 PermGen 被元空间代替，永久代的<strong>类信息、方法、常量池</strong>等都移动到元空间区</p><p>元空间与永久代区别：元空间不在虚拟机中，使用的本地内存，默认情况下，元空间的大小仅受本地内存限制</p><p>方法区内存溢出：</p><ul><li><p>JDK1.8 以前会导致<strong>永久代</strong>内存溢出：java.lang.OutOfMemoryError: PerGen space</p><pre class="line-numbers language-sh"><code class="language-sh"> -XX:MaxPermSize=8m#参数设置<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre></li><li><p>JDK1.8 以后会导致<strong>元空间</strong>内存溢出：java.lang.OutOfMemoryError: Metaspace</p><pre class="line-numbers language-sh"><code class="language-sh">-XX:MaxMetaspaceSize=8m#参数设置<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre></li></ul><p>元空间内存溢出演示：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">/** * 演示元空间内存溢出 java.lang.OutOfMemoryError: Metaspace * -XX:MaxMetaspaceSize=8m */</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Demo1_8</span> <span class="token keyword">extends</span> <span class="token class-name">ClassLoader</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// 可以用来加载类的二进制字节码</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">int</span> j <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>        <span class="token keyword">try</span> <span class="token punctuation">{</span>            Demo1_8 test <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Demo1_8</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> <span class="token number">10000</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">,</span> j<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token comment" spellcheck="true">// ClassWriter 作用是生成类的二进制字节码</span>                ClassWriter cw <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ClassWriter</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token comment" spellcheck="true">// 版本号， public， 类名, 包名, 父类， 接口</span>                cw<span class="token punctuation">.</span><span class="token function">visit</span><span class="token punctuation">(</span>Opcodes<span class="token punctuation">.</span>V1_8<span class="token punctuation">,</span> Opcodes<span class="token punctuation">.</span>ACC_PUBLIC<span class="token punctuation">,</span> <span class="token string">"Class"</span> <span class="token operator">+</span> i<span class="token punctuation">,</span> null<span class="token punctuation">,</span> <span class="token string">"java/lang/Object"</span><span class="token punctuation">,</span> null<span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token comment" spellcheck="true">// 返回 byte[]</span>                <span class="token keyword">byte</span><span class="token punctuation">[</span><span class="token punctuation">]</span> code <span class="token operator">=</span> cw<span class="token punctuation">.</span><span class="token function">toByteArray</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token comment" spellcheck="true">// 执行了类的加载</span>                test<span class="token punctuation">.</span><span class="token function">defineClass</span><span class="token punctuation">(</span><span class="token string">"Class"</span> <span class="token operator">+</span> i<span class="token punctuation">,</span> code<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> code<span class="token punctuation">.</span>length<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// Class 对象</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>            System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>j<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="2-直接内存"><a href="#2-直接内存" class="headerlink" title="2.直接内存"></a>2.直接内存</h3><p>直接内存是 Java 堆外、直接向系统申请的内存区间，不是虚拟机运行时数据区的一部分，也不是《Java 虚拟机规范》中定义的内存区域，但是这部分内存也被频繁地使用。而且也可能导致 OutOfMemoryError 错误出现。</p><h4 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h4><p>Direct Memory 优点：</p><ul><li>Java 的 NIO 库允许 Java 程序使用直接内存，使用 native 函数直接分配堆外内存</li><li><strong>读写性能高</strong>，读写频繁的场合可能会考虑使用直接内存</li><li>大大提高 IO 性能，避免了在 Java 堆和 native 堆来回复制数据</li></ul><p>直接内存缺点：</p><ul><li>不能使用内核缓冲区 Page Cache 的缓存优势，无法缓存最近被访问的数据和使用预读功能</li><li>分配回收成本较高，不受 JVM 内存回收管理</li><li>可能导致 OutOfMemoryError 异常：OutOfMemoryError: Direct buffer memory</li><li>回收依赖 System.gc() 的调用，但这个调用 JVM 不保证执行、也不保证何时执行，行为是不可控的。程序一般需要自行管理，成对去调用 malloc、free</li></ul><p>应用场景：</p><ul><li>传输很大的数据文件，数据的生命周期很长，导致 Page Cache 没有起到缓存的作用，一般采用直接 IO 的方式</li><li>适合频繁的 IO 操作，比如网络并发场景</li></ul><p>数据流的角度：</p><ul><li>非直接内存的作用链：本地 IO → 内核缓冲区→ 用户（JVM）缓冲区 →内核缓冲区 → 本地 IO</li><li>直接内存的作用链：本地 IO → 直接内存 → 本地 IO</li></ul><table><thead><tr><th>直接内存</th><th><img src="https://img.jwt1399.top/img/202212071545546.png"></th></tr></thead><tbody><tr><td><strong>非直 接内存</strong></td><td><img src="https://img.jwt1399.top/img/202212071545613.png"></td></tr></tbody></table><h4 id="ByteBuffer"><a href="#ByteBuffer" class="headerlink" title="ByteBuffer"></a>ByteBuffer</h4><img src="https://img.jwt1399.top/img/202212071605453.png" style="zoom: 50%;" /><p>ByteBuffer 有两种类型：</p><ul><li>一种是基于直接内存（非堆内存）：DirectByteBuffer</li><li>一种是非直接内存（堆内存）：HeapByteBuffer</li></ul><table><thead><tr><th></th><th>描述</th><th>优点</th></tr></thead><tbody><tr><td>HeapByteBuffer</td><td>在jvm堆上面的一个buffer，底层的本质是一个数组</td><td>由于内容维护在jvm里，所以把内容写进buffer里速度会快些；并且，可以更容易回收</td></tr><tr><td>DirectByteBuffer</td><td>底层的数据其实是维护在操作系统的内存中，而不是jvm里，DirectByteBuffer里维护了一个引用address指向了数据，从而操作数据</td><td>跟外设（IO设备）打交道时会快很多，因为外设读取jvm堆里的数据时，不是直接读取的，而是把jvm里的数据读到一个内存块里，再在这个块里读取的，如果使用DirectByteBuffer，则可以省去这一步，实现zero copy</td></tr></tbody></table><h4 id="分配回收"><a href="#分配回收" class="headerlink" title="分配回收"></a>分配回收</h4><p>直接内存 DirectByteBuffer 源码分析：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token function">DirectByteBuffer</span><span class="token punctuation">(</span><span class="token keyword">int</span> cap<span class="token punctuation">)</span> <span class="token punctuation">{</span>     <span class="token comment" spellcheck="true">//....</span>    <span class="token keyword">long</span> base <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>    <span class="token keyword">try</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 分配直接内存</span>        base <span class="token operator">=</span> unsafe<span class="token punctuation">.</span><span class="token function">allocateMemory</span><span class="token punctuation">(</span>size<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">// 内存赋值</span>    unsafe<span class="token punctuation">.</span><span class="token function">setMemory</span><span class="token punctuation">(</span>base<span class="token punctuation">,</span> size<span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token keyword">byte</span><span class="token punctuation">)</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>pa <span class="token operator">&amp;&amp;</span> <span class="token punctuation">(</span>base <span class="token operator">%</span> ps <span class="token operator">!=</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        address <span class="token operator">=</span> base <span class="token operator">+</span> ps <span class="token operator">-</span> <span class="token punctuation">(</span>base <span class="token operator">&amp;</span> <span class="token punctuation">(</span>ps <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>        address <span class="token operator">=</span> base<span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">// 创建回收函数</span>    cleaner <span class="token operator">=</span> Cleaner<span class="token punctuation">.</span><span class="token function">create</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name">Deallocator</span><span class="token punctuation">(</span>base<span class="token punctuation">,</span> size<span class="token punctuation">,</span> cap<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">Deallocator</span> <span class="token keyword">implements</span> <span class="token class-name">Runnable</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">run</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        unsafe<span class="token punctuation">.</span><span class="token function">freeMemory</span><span class="token punctuation">(</span>address<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 释放内存</span>        <span class="token comment" spellcheck="true">//...</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>分配和回收原理</strong>：</p><ul><li>使用了 <code>Unsafe</code> 对象的 <code>allocateMemory</code> 方法完成<strong>直接内存的分配</strong>，setMemory 方法完成赋值</li><li>ByteBuffer 的实现类内部，使用了 Cleaner（虚引用）来监测 ByteBuffer 对象，一旦 ByteBuffer 对象被垃圾回收，那么 ReferenceHandler 线程通过 Cleaner 的 clean 方法调用 Deallocator 的 run方法，最后通过 <code>freeMemory</code> 来<strong>释放直接内存</strong></li></ul><h2 id="❸JVM运行原理"><a href="#❸JVM运行原理" class="headerlink" title="❸JVM运行原理"></a>❸JVM运行原理</h2><img src="https://img.jwt1399.top/img/202212230857858.png" style="zoom:50%;" /><p>接下来，我们通过一个案例来了解下代码和对象是如何分配存储的，Java 代码又是如何在 JVM 中运行的。</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">JVMCase</span> <span class="token punctuation">{</span>  <span class="token comment" spellcheck="true">// 常量</span>  <span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token keyword">static</span> String MAN_SEX_TYPE <span class="token operator">=</span> <span class="token string">"man"</span><span class="token punctuation">;</span>  <span class="token comment" spellcheck="true">// 静态变量</span>  <span class="token keyword">public</span> <span class="token keyword">static</span> String WOMAN_SEX_TYPE <span class="token operator">=</span> <span class="token string">"woman"</span><span class="token punctuation">;</span>  <span class="token comment" spellcheck="true">// 静态方法</span>  <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">print</span><span class="token punctuation">(</span>Student stu<span class="token punctuation">)</span> <span class="token punctuation">{</span>    System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"name: "</span> <span class="token operator">+</span> stu<span class="token punctuation">.</span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">"; sex:"</span> <span class="token operator">+</span> stu<span class="token punctuation">.</span><span class="token function">getSexType</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">"; age:"</span> <span class="token operator">+</span> stu<span class="token punctuation">.</span><span class="token function">getAge</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>   <span class="token punctuation">}</span>  <span class="token comment" spellcheck="true">// 非静态方法</span>  <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">sayHello</span><span class="token punctuation">(</span>Student stu<span class="token punctuation">)</span> <span class="token punctuation">{</span>    System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>stu<span class="token punctuation">.</span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">"say: hello"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>   <span class="token punctuation">}</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>    Student stu <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Student</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    stu<span class="token punctuation">.</span><span class="token function">setName</span><span class="token punctuation">(</span><span class="token string">"nick"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    stu<span class="token punctuation">.</span><span class="token function">setSexType</span><span class="token punctuation">(</span>MAN_SEX_TYPE<span class="token punctuation">)</span><span class="token punctuation">;</span>    stu<span class="token punctuation">.</span><span class="token function">setAge</span><span class="token punctuation">(</span><span class="token number">20</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        JVMCase jvmcase <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">JVMCase</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 调用静态方法</span>    <span class="token function">print</span><span class="token punctuation">(</span>stu<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 调用非静态方法</span>    jvmcase<span class="token punctuation">.</span><span class="token function">sayHello</span><span class="token punctuation">(</span>stu<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token annotation punctuation">@Data</span><span class="token keyword">class</span> <span class="token class-name">Student</span><span class="token punctuation">{</span>  String name<span class="token punctuation">;</span>  String sexType<span class="token punctuation">;</span>  <span class="token keyword">int</span> age<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>运行上面代码时，JVM的整个处理过程如下：</p><ul><li>1.JVM 向操作系统<strong>申请内存</strong>，JVM 首先通过配置参数或者默认配置参数向操作系统申请内存空间，根据内存大小找到具体的内存分配表，然后把内存段的起始地址和终止地址分配给 JVM。</li><li>2.JVM 获得内存空间后，就<strong>进行内部分配</strong>。JVM 根据配置参数分配堆、栈以及方法区的内存大小。</li><li>3.<strong>class 文件加载、验证、准备以及解析</strong>，其中准备阶段会为类的静态变量分配内存，初始化为系统的初始值</li></ul><img src="https://img.jwt1399.top/img/202212061913505.jpg" style="zoom:50%;" /><ul><li><p>4.完成上一个步骤后，将会进行最后一个初始化阶段。在这个阶段中，JVM 首先会执行构造器<code>&lt;clinit&gt;()</code>方法，编译器会在.java 文件被编译成.class 文件时，收集所有类的初始化代码，包括静态变量赋值语句、静态代码块、静态方法，收集在一起成为<code>&lt;clinit&gt;()</code>方法。</p><img src="https://img.jwt1399.top/img/202212061913531.jpg" style="zoom:50%;" /></li><li><p>5.执行方法。启动 main 线程，执行 main 方法，开始执行第一行代码。此时堆内存中会创建一个 student 对象，对象引用 student 就存放在栈中。<img src="https://img.jwt1399.top/img/202212061923027.jpg" style="zoom:50%;" /></p></li><li><p>6.创建一个 JVMCase 对象，调用 sayHello 非静态方法，sayHello 方法属于对象 JVMCase，此时 sayHello 方法入栈，并通过栈中的 student 引用调用堆中的 Student 对象；之后，调用静态方法 print，print 静态方法属于 JVMCase 类，是从静态方法中获取，之后放入到栈中，也通过 student 引用调用堆中的 student 对象。</p></li></ul><img src="https://img.jwt1399.top/img/202212061923172.jpg" style="zoom:50%;" /><h2 id="❹总结"><a href="#❹总结" class="headerlink" title="❹总结"></a>❹总结</h2><h3 id="1-异常"><a href="#1-异常" class="headerlink" title="1.异常"></a>1.异常</h3><p>常见 Out Of Memory（OOM） 错误：</p><ul><li><p>java.lang.StackOverflowError</p><ul><li>栈溢出</li><li>设置栈内存大小：<code>-Xss size</code></li></ul></li><li><p>java.lang.OutOfMemoryError：java heap space</p><ul><li>堆溢出</li><li>设置堆内存指令：<code>-Xmx Size</code></li></ul></li><li><p>java.lang.OutOfMemoryError：GC overhead limit exceeded  </p><ul><li>当 JVM 花太多时间执行垃圾回收并且只能回收很少的堆空间时，就会发生此错误。</li><li>即程序基本上耗尽了所有的可用内存, GC也清理不了。</li></ul></li><li><p>java.lang.OutOfMemoryError：Direct buffer memory</p><ul><li>直接内存溢出</li></ul></li><li><p>java.lang.OutOfMemoryError：unable to create new native thread</p><ul><li>系统内存耗尽，无法为新线程分配内存或者创建线程数超过了操作系统的限制</li></ul></li><li><p>java.lang.OutOfMemoryError: PermGen space</p><ul><li>Java 1.8 以前：永久代内存溢出 </li><li><code>-XX:MaxPermSize=8m</code></li></ul><p></p></li><li><p>java.lang.OutOfMemoryError：Metaspace</p><ul><li>Java 1.8 之后：元空间内存溢出</li><li><code>-XX:MaxMetaspaceSize=8m</code></li></ul></li></ul><p></p><p>Java 编译指令：<code>javac -g 文件名.java</code>  -g 可以生成所有相关信息</p><p>Java 反编译指令：<code>javap -v 文件名.class</code> -v 输出附加信息</p><p>后台运行：<code>nohup java 全路径名</code></p><h3 id="2-三种常量池"><a href="#2-三种常量池" class="headerlink" title="2.三种常量池"></a>2.三种常量池</h3><p><strong>常量池</strong>：主要存放编译期生成的各种**字面量(Literal)和符号引用(Symbolic References)**。</p><ul><li>字面量：例如文本字符串、fina修饰的常量。</li><li>符号引用：例如类和接口的全限定名、字段的名称和描述符、方法的名称和描述符</li></ul><p><strong>运行时常量池</strong>：运行时常量池里面存储的主要是<strong>编译期间生成的字面量、符号引用</strong>等等。</p><ul><li>当类加载到内存中后，JVM就会将<strong>常量池</strong>中的内容存放到运行时常量池中；</li><li>类加载在链接环节的解析过程，会符号引用转换成直接引用（静态链接）。此处得到的<strong>直接引用</strong>也是放到运行时常量池中的。</li><li>运行期间可以动态放入新的常量。</li></ul><p><strong>字符串常量池</strong>：可以理解成运行时常量池分出来的一部分。类加载到内存的时候，字符串会存到字符串常量池里面。 </p><ul><li>JVM 为了提升性能和减少内存消耗针对字符串（String 类）专门开辟的一块区域，主要目的是为了避免字符串的重复创建。</li><li>JDK6时字符串常量池位于运行时常量池，JDK7挪到堆中。</li></ul><p><strong>3者区别？</strong></p><ul><li>常量池与运行时常量池都存储在<strong>方法区</strong>，而字符串常量池在 Jdk7 时就已经从方法区迁移到了 Java 堆中</li></ul><p>在类编译过程中，会把类元信息放到方法区，类元信息的其中一部分便是常量池，主要存放字面量和符号引用，而字面量的一部分便是文本字符；<strong>在类加载时将字面量和符号引用解析为直接引用存储在运行时常量池</strong>；对于文本字符，会在解析时查找字符串常量池，查出这个文本字符对应的字符串对象的直接引用，将直接引用存储在运行时常量池</p><h3 id="3-变量位置"><a href="#3-变量位置" class="headerlink" title="3.变量位置"></a>3.变量位置</h3><p>变量的位置不取决于它是基本数据类型还是引用数据类型，取决于它的<strong>声明位置</strong></p><p>静态内部类和其他内部类：方法区&#x2F;堆</p><ul><li><p><strong>一个 class 文件只能对应一个 public 类型的类</strong>，这个类可以有内部类，但不会生成新的 class 文件</p></li><li><p>静态内部类属于类本身，加载到<strong>方法区</strong>，其他内部类属于内部类的属性，加载到<strong>堆</strong></p></li></ul><p>类变量：堆</p><ul><li>类变量是用 static 修饰符修饰，定义在方法外的变量，随着 Java 进程产生和销毁</li><li>在 Java8 之前把静态变量存放于方法区，在 Java8 时存放在<strong>堆中的静态变量区</strong></li></ul><p>实例变量：堆</p><ul><li>实例（成员）变量是定义在类中，没有 static 修饰的变量，随着类的实例产生和销毁，是类实例的一部分</li><li>在类初始化的时候，从运行时常量池取出直接引用或者值，<strong>与初始化的对象一起放入堆中</strong></li></ul><p>局部变量：虚拟机栈</p><ul><li>局部变量是定义在类的方法中的变量</li><li>在所在方法被调用时<strong>放入虚拟机栈的栈帧</strong>中，方法执行结束后从虚拟机栈中弹出</li></ul><h1 id="③对象实例化"><a href="#③对象实例化" class="headerlink" title="③对象实例化"></a>③对象实例化</h1><h2 id="❶对象内存结构"><a href="#❶对象内存结构" class="headerlink" title="❶对象内存结构"></a>❶对象内存结构</h2><p>一个 Java 对象内存中存储为三部分：对象头（Header）、实例数据（Instance Data）和对齐填充 （Padding）</p><p><strong>对象头：</strong></p><ul><li>普通对象：分为 <code>Mark Word</code> 和 <code>Klass Word</code> 两部分</li></ul><pre class="line-numbers language-ruby"><code class="language-ruby"><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">|</span><span class="token operator">|</span>                         <span class="token builtin">Object</span> <span class="token function">Header</span> <span class="token punctuation">(</span><span class="token number">64</span> bits<span class="token punctuation">)</span>              <span class="token operator">|</span><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">-</span><span class="token operator">|</span><span class="token operator">|</span>           <span class="token constant">Mark</span> <span class="token function">Word</span> <span class="token punctuation">(</span><span class="token number">32</span> bits<span class="token punctuation">)</span>      <span class="token operator">|</span>    <span class="token constant">Klass</span> <span class="token function">Word</span> <span class="token punctuation">(</span><span class="token number">32</span> bits<span class="token punctuation">)</span> <span class="token operator">|</span><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">-</span><span class="token operator">|</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><code>Mark Word</code>：用于存储对象自身的运行时数据， 如哈希码（HashCode）、GC 分代年龄、锁状态标志（最后两位）、线程持有的锁、偏向线程ID、偏向时间戳等等</p><p>32 位虚拟机 Mark Word</p><pre class="line-numbers language-ruby"><code class="language-ruby"><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">-</span><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">|</span><span class="token operator">|</span>                    <span class="token constant">Mark</span> <span class="token function">Word</span> <span class="token punctuation">(</span><span class="token number">32</span> bits<span class="token punctuation">)</span>                <span class="token operator">|</span>        <span class="token constant">State</span>       <span class="token operator">|</span><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">-</span><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">|</span><span class="token operator">|</span>              hashcode<span class="token punctuation">:</span><span class="token number">25</span> <span class="token operator">|</span> age<span class="token punctuation">:</span><span class="token number">4</span> <span class="token operator">|</span> biased_lock<span class="token punctuation">:</span><span class="token number">0</span> <span class="token operator">|</span> <span class="token number">01</span> <span class="token operator">|</span>        <span class="token constant">Normal</span>      <span class="token operator">|</span><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">-</span><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">|</span><span class="token operator">|</span>      thread<span class="token punctuation">:</span><span class="token number">23</span> <span class="token operator">|</span> epoch<span class="token punctuation">:</span><span class="token number">2</span> <span class="token operator">|</span> age<span class="token punctuation">:</span><span class="token number">4</span> <span class="token operator">|</span> biased_lock<span class="token punctuation">:</span><span class="token number">1</span> <span class="token operator">|</span> <span class="token number">01</span> <span class="token operator">|</span>        <span class="token constant">Biased</span>      <span class="token operator">|</span><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">-</span><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">|</span><span class="token operator">|</span>                            ptr_to_lock_record<span class="token punctuation">:</span><span class="token number">30</span> <span class="token operator">|</span> <span class="token number">00</span> <span class="token operator">|</span> <span class="token constant">Lightweight</span> <span class="token constant">Locked</span> <span class="token operator">|</span><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">-</span><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">|</span><span class="token operator">|</span>                    ptr_to_heavyweight_monitor<span class="token punctuation">:</span><span class="token number">30</span> <span class="token operator">|</span> <span class="token number">10</span> <span class="token operator">|</span> <span class="token constant">Heavyweight</span> <span class="token constant">Locked</span> <span class="token operator">|</span><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">-</span><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">|</span><span class="token operator">|</span>                                                  <span class="token operator">|</span> <span class="token number">11</span> <span class="token operator">|</span>    <span class="token constant">Marked</span> <span class="token keyword">for</span> <span class="token constant">GC</span>   <span class="token operator">|</span><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">-</span><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">|</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>64 位虚拟机 Mark Word</p><pre class="line-numbers language-ruby"><code class="language-ruby"><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">|</span><span class="token operator">|</span>                      <span class="token constant">Mark</span> <span class="token function">Word</span> <span class="token punctuation">(</span><span class="token number">64</span> bits<span class="token punctuation">)</span>                           <span class="token operator">|</span>       <span class="token constant">State</span>        <span class="token operator">|</span><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">|</span><span class="token operator">|</span>    unused<span class="token punctuation">:</span><span class="token number">25</span> <span class="token operator">|</span> hashcode<span class="token punctuation">:</span><span class="token number">31</span> <span class="token operator">|</span> unused<span class="token punctuation">:</span><span class="token number">1</span> <span class="token operator">|</span> age<span class="token punctuation">:</span><span class="token number">4</span> <span class="token operator">|</span> biased_lock<span class="token punctuation">:</span><span class="token number">0</span> <span class="token operator">|</span> <span class="token number">01</span> <span class="token operator">|</span>       <span class="token constant">Normal</span>       <span class="token operator">|</span><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">|</span><span class="token operator">|</span>        thread<span class="token punctuation">:</span><span class="token number">54</span> <span class="token operator">|</span> epoch<span class="token punctuation">:</span><span class="token number">2</span> <span class="token operator">|</span> unused<span class="token punctuation">:</span><span class="token number">1</span> <span class="token operator">|</span> age<span class="token punctuation">:</span><span class="token number">4</span> <span class="token operator">|</span> biased_lock<span class="token punctuation">:</span><span class="token number">1</span> <span class="token operator">|</span> <span class="token number">01</span> <span class="token operator">|</span>       <span class="token constant">Biased</span>       <span class="token operator">|</span><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">|</span><span class="token operator">|</span>                                         ptr_to_lock_record<span class="token punctuation">:</span><span class="token number">62</span> <span class="token operator">|</span> <span class="token number">00</span> <span class="token operator">|</span> <span class="token constant">Lightweight</span> <span class="token constant">Locked</span> <span class="token operator">|</span><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">|</span><span class="token operator">|</span>                                 ptr_to_heavyweight_monitor<span class="token punctuation">:</span><span class="token number">62</span> <span class="token operator">|</span> <span class="token number">10</span> <span class="token operator">|</span> <span class="token constant">Heavyweight</span> <span class="token constant">Locked</span> <span class="token operator">|</span><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">|</span><span class="token operator">|</span>                                                               <span class="token operator">|</span> <span class="token number">11</span> <span class="token operator">|</span>    <span class="token constant">Marked</span> <span class="token keyword">for</span> <span class="token constant">GC</span>   <span class="token operator">|</span><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">|</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-ruby"><code class="language-ruby"><span class="token function">hash</span><span class="token punctuation">(</span><span class="token number">25</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token function">age</span><span class="token punctuation">(</span><span class="token number">4</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token function">lock</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span> <span class="token operator">=</span> 32bit<span class="token comment" spellcheck="true">#32位系统</span><span class="token function">unused</span><span class="token punctuation">(</span><span class="token number">25</span><span class="token operator">+</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token function">hash</span><span class="token punctuation">(</span><span class="token number">31</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token function">age</span><span class="token punctuation">(</span><span class="token number">4</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token function">lock</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span> <span class="token operator">=</span> 64bit<span class="token comment" spellcheck="true">#64位系统</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p><code>Klass Word</code>：类型指针，<strong>指向该对象的 Class 类对象的指针</strong>，虚拟机通过这个指针来确定这个对象是哪个类的实例；在 64 位系统中，默认开启指针压缩（<code>-XX:+UseCompressedOops</code>），使用32bits指针。堆内存大于32G时，压缩指针会失效，会强制使用64bits来进行对象寻址</p><ul><li>数组对象：如果对象是一个数组，那在对象头中还有一块数据用于记录数组长度（12 字节）</li></ul><pre class="line-numbers language-ruby"><code class="language-ruby"><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">-</span><span class="token operator">|</span><span class="token operator">|</span>               <span class="token builtin">Object</span> <span class="token function">Header</span> <span class="token punctuation">(</span><span class="token number">96</span> bits<span class="token punctuation">)</span>               <span class="token operator">|</span><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">-</span><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">-</span><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">-</span><span class="token operator">|</span><span class="token operator">|</span>  <span class="token constant">Mark</span> <span class="token function">Word</span><span class="token punctuation">(</span>32bits<span class="token punctuation">)</span>    <span class="token operator">|</span>   <span class="token constant">Klass</span> <span class="token function">Word</span><span class="token punctuation">(</span>32bits<span class="token punctuation">)</span>     <span class="token operator">|</span>   array <span class="token function">length</span><span class="token punctuation">(</span>32bits<span class="token punctuation">)</span>  <span class="token operator">|</span><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">-</span><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">-</span><span class="token operator">|</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">--</span><span class="token operator">-</span><span class="token operator">|</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>实例数据</strong>：实例数据部分是对象真正存储的有效信息，也是在程序代码中所定义的各种类型的字段内容，无论是从父类继承下来的，还是在子类中定义的，都需要记录起来</p><p><strong>对齐填充</strong>：Padding 起占位符的作用。64 位系统，由于 HotSpot VM 的自动内存管理系统要求<strong>对象起始地址必须是 8 字节的整数倍</strong>，就是对象的大小必须是 8 字节的整数倍，而对象头部分正好是 8 字节的倍数（1 倍或者 2 倍），因此当对象实例数据部分没有对齐时，就需要通过对齐填充来补全</p><p>32 位系统：</p><ul><li><p>一个 int 在 java 中占据 4byte，所以 Integer 的大小为：</p><pre class="line-numbers language-ruby"><code class="language-ruby"><span class="token comment" spellcheck="true"># 需要补位4byte</span><span class="token function">4</span><span class="token punctuation">(</span><span class="token constant">Mark</span> <span class="token constant">Word</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token function">4</span><span class="token punctuation">(</span><span class="token constant">Klass</span> <span class="token constant">Word</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token function">4</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token function">4</span><span class="token punctuation">(</span><span class="token constant">Padding</span><span class="token punctuation">)</span> <span class="token operator">=</span> 16byte<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre></li><li><p><code>int[] arr = new int[10]</code></p><pre class="line-numbers language-ruby"><code class="language-ruby"><span class="token comment" spellcheck="true"># 由于需要8位对齐，所以最终大小为56byte</span><span class="token function">4</span><span class="token punctuation">(</span><span class="token constant">Mark</span> <span class="token constant">Word</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token function">4</span><span class="token punctuation">(</span><span class="token constant">Klass</span> <span class="token constant">Word</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token function">4</span><span class="token punctuation">(</span>length<span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token number">4</span><span class="token operator">*</span><span class="token function">10</span><span class="token punctuation">(</span><span class="token number">10</span>个int大小<span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token function">4</span><span class="token punctuation">(</span><span class="token constant">Padding</span><span class="token punctuation">)</span> <span class="token operator">=</span> 56sbyte<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre></li></ul><img src="https://img.jwt1399.top/img/202212221542828.png" style="zoom: 50%;" /><h2 id="❷对象访问方式"><a href="#❷对象访问方式" class="headerlink" title="❷对象访问方式"></a>❷对象访问方式</h2><p>JVM 是通过<strong>栈帧中的对象引用（reference）</strong>访问到堆中的对象实例：</p><ul><li><p>句柄访问：Java 堆中会划分出一块内存来作为句柄池，reference 中存储的就是对象的句柄地址，而句柄中包含了对象实例数据和类型数据各自的具体地址信息</p><p>优点：reference 中存储的是稳定的句柄地址，在对象被移动（垃圾收集）时只会改变句柄中的实例数据指针，而 reference 本身不需要被修改</p><p><img src="https://img.jwt1399.top/img/202212221546450.jpeg"></p></li><li><p>直接指针（HotSpot 采用）：reference 中直接存储的对象地址，对象中包含对象类型数据的指针，通过这个指针可以访问对象类型数据。</p><p>优点：速度更快，<strong>节省了一次指针定位的时间开销</strong></p><p>缺点：对象被移动时（如进行 GC 后的内存重新排列），对象的 reference 也需要同步更新</p><p><img src="https://img.jwt1399.top/img/202212221547695.jpeg"></p></li></ul><h2 id="❸对象创建过程"><a href="#❸对象创建过程" class="headerlink" title="❸对象创建过程"></a>❸对象创建过程</h2><p><strong>创建对象的方式</strong></p><ul><li><p>new：最常见的方式</p></li><li><p>Class的newInstance方法（反射机制）</p></li><li><p>Constructor的newInstance(XXX)（反射机制）</p></li><li><p>使用clone()：不调用任何的构造器，要求当前的类需要实现Cloneable接口，实现clone()</p></li><li><p>使用序列化：从文件中、从网络中获取一个对象的二进制流</p></li><li><p>第三方库 Objenesis</p></li></ul><p><strong>创建对象的过程</strong></p><ol><li>类加载检查</li></ol><p>当虚拟机遇到一条 <code>new</code> 指令时，首先检查是否能在运行时常量池中定位到这个类的符号引用，并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有，那先执行类加载。</p><ol start="2"><li>为对象分配内存</li></ol><p>首先计算对象占用空间的大小，接着在堆中划分一块内存给新对象。</p><ul><li><p><strong>如果内存规整</strong>：虚拟机将采用的是指针碰撞法（Bump The Point）来为对象分配内存。</p></li><li><p><strong>如果内存不规整</strong>：虚拟机需要维护一个空闲列表（Free List）来为对象分配内存。</p></li></ul><p>选择哪种分配方式由堆是否规整所决定，而堆是否规整又由所采用的GC收集器是否带有压缩整理功能决定。</p><p><strong>内存分配并发问题</strong></p><p>在创建对象的时候有一个很重要的问题，就是线程安全，虚拟机采用两种方式来保证线程安全：</p><ul><li><strong>CAS+失败重试：</strong> CAS 是乐观锁的一种实现方式。所谓乐观锁就是，每次不加锁而是假设没有冲突而去完成某项操作，如果因为冲突失败就重试，直到成功为止。<strong>虚拟机采用 CAS 配上失败重试的方式保证更新操作的原子性。</strong></li><li><strong>TLAB：</strong> 为每一个线程预先在 Eden 区分配一块内存，JVM 在给线程中的对象分配内存时，首先在 TLAB 分配，当对象大于 TLAB 中的剩余内存或 TLAB 的内存已用尽时，再采用上述的 CAS 进行内存分配，通过设置 <code>-XX:+UseTLAB</code>参数来设定</li></ul><ol start="3"><li>初始化零值</li></ol><p>分配到的内存空间都初始化为零值，通过这个操作保证了对象的字段可以不赋初始值就直接使用，程序能访问到这些字段的数据类型所对应的零值。</p><ol start="4"><li>设置对象头</li></ol><p>将对象的所属类（即类的元数据信息）、对象的哈希码、对象的GC分代年龄、锁信息等数据存储在对象的对象头中。另外，根据虚拟机当前运行状态的不同，如是否启用偏向锁等，对象头会有不同的设置方式。</p><ol start="5"><li>执行 init 方法</li></ol><p>在上面工作都完成之后，从虚拟机的视角来看，一个新的对象已经产生了，但从 Java 程序的视角来看，对象创建才刚开始，执行 new 指令之后会接着执行 <code>&lt;init&gt;</code> 方法（初始化成员变量，执行实例化代码块，调用类的构造方法，并把堆内对象的首地址赋值给引用变量），把对象按照程序员的意愿进行初始化，这样一个真正可用的对象才算完全产生出来。</p><h1 id="③垃圾回收"><a href="#③垃圾回收" class="headerlink" title="③垃圾回收"></a>③垃圾回收</h1><h2 id="❶内存分配"><a href="#❶内存分配" class="headerlink" title="❶内存分配"></a>❶内存分配</h2><h3 id="1-两种方式"><a href="#1-两种方式" class="headerlink" title="1.两种方式"></a>1.两种方式</h3><p>JVM 为对象分配内存的过程：首先计算对象占用空间大小，接着在堆中划分一块内存给新对象</p><ul><li>如果内存规整，使用指针碰撞（Bump The Pointer）。所有用过的内存在一边，空闲的内存在另外一边，中间有一个指针作为分界点的指示器，分配内存就仅仅是把指针向空闲那边挪动一段与对象大小相等的距离</li><li>如果内存不规整，虚拟机维护一个空闲列表（Free List）。已使用的内存和未使用的内存相互交错，列表上记录哪些内存块是可用的，分配的时候从列表中找到一块足够大的空间划分给对象实例，并更新列表上的内容</li></ul><h3 id="2-TLAB"><a href="#2-TLAB" class="headerlink" title="2.TLAB"></a>2.TLAB</h3><p>Thread Local Allocation Buffer，TLAB 是虚拟机在堆内存的 Eden 划分出来的一块专用空间，是线程专属的。</p><p>在线程初始化时，虚拟机会为每个线程分配一块TLAB空间，只给当前线程使用，这样每个线程都单独拥有一个空间，如果需要分配内存，就在自己的空间上分配，这样就不存在竞争的情况，可以大大提升分配效率。</p><p>多线程分配内存时，使用 TLAB 可以避免线程安全问题，同时还能够提升内存分配的吞吐量，这种内存分配方式叫做<strong>快速分配策略</strong></p><p>我们说TLAB是线程独享的，但是只是在“分配”这个动作上是线程独享的，至于在读取、垃圾回收等动作上都是线程共享的。</p><ul><li>栈上分配使用的是栈来进行对象内存的分配</li><li>TLAB 分配使用的是 Eden 区域进行内存分配，属于堆内存</li></ul><p>堆区是线程共享区域，任何线程都可以访问到堆区中的共享数据，由于对象实例的创建在 JVM 中非常频繁，因此在并发环境下为避免多个线程操作同一地址，需要使用加锁等机制，进而影响分配速度</p><p>问题：堆空间都是共享的么？ 不一定，因为还有 TLAB，在堆中划分出一块区域，为每个线程所独占</p><p><img src="https://img.jwt1399.top/img/202212181344818.jpg"></p><p>JVM 是将 TLAB 作为内存分配的首选，但不是所有的对象实例都能够在 TLAB 中成功分配内存，一旦对象在 TLAB 空间分配内存失败时，JVM 就会通过<strong>使用加锁机制确保数据操作的原子性</strong>，从而直接在堆中分配内存</p><p>栈上分配优先于 TLAB 分配进行，逃逸分析中若可进行栈上分配优化，会优先进行对象栈上直接分配内存</p><p>参数设置：</p><ul><li><p><code>-XX:UseTLAB</code>：设置是否开启 TLAB 空间</p></li><li><p><code>-XX:TLABWasteTargetPercent</code>：设置 TLAB 空间所占用 Eden 空间的百分比大小，默认情况下 TLAB 空间的内存非常小，仅占有整个 Eden 空间的1%</p></li><li><p><code>-XX:TLABRefillWasteFraction</code>：指当 TLAB 空间不足，请求分配的对象内存大小超过此阈值时不会进行 TLAB 分配，直接进行堆内存分配，否则还是会优先进行 TLAB 分配</p></li></ul><p><img src="https://img.jwt1399.top/img/202212181344195.jpg"></p><h2 id="❷分代思想"><a href="#❷分代思想" class="headerlink" title="❷分代思想"></a>❷分代思想</h2><h3 id="1-分代介绍"><a href="#1-分代介绍" class="headerlink" title="1.分代介绍"></a>1.分代介绍</h3><p>Java8 时，堆被分为了两份：新生代和老年代（1:2），在 Java7 时，还存在一个永久代</p><ul><li>新生代使用：复制算法</li><li>老年代使用：标记 - 清除 或者 标记 - 整理 算法</li></ul><p>Young 区被划分为三部分，Eden 区和两个大小严格相同的 Survivor 区。 Eden 和 Survivor 大小比例默认为 8:1:1</p><ul><li>Survivor 区某一时刻只有其中一个是被使用的，另外一个留做垃圾回收时复制对象。</li><li>在 Eden 区变满的时候，GC 就会将存活的对象移到空闲的 Survivor 区间中，根据 JVM 的策略，在经过几次垃圾回收后，仍然存活于 Survivor 的对象将被移动到 Old 区间</li></ul><p>Old 区主要保存生命周期长的对象，一般是一些老的对象，当一些对象在 Young 复制转移一定的次数以后，对象就会被转移到 Old 区</p><img src="https://img.jwt1399.top/img/202212071718619.png"  /><p>分代原因：不同对象的生命周期不同，70%-99% 的对象都是临时对象，优化 GC 性能</p><p><strong>GC</strong>：</p><ul><li>Minor GC：回收新生代，新生代对象存活时间很短，所以 Minor GC 会频繁执行，执行的速度比较快</li><li>Major GC：回收老年代。目前只有CMS收集器会有单独收集老年代的行为。</li><li>Mixed GC：回收整个新生代以及部分老年代的垃圾收集。目前只有G1收集器会有这种行为。</li><li>Full GC：回收整个Java堆和方法区。回收老年代和新生代，老年代对象其存活时间长，所以 Full GC 很少执行，执行速度会比 Minor GC 慢很多</li></ul><hr><h3 id="2-分代分配"><a href="#2-分代分配" class="headerlink" title="2.分代分配"></a>2.分代分配</h3><p>工作机制：</p><ul><li><strong>对象优先在 Eden 分配</strong>：当创建一个对象的时候，对象会被分配在新生代的 Eden 区，当 Eden 区要满了时候，触发 Minor GC</li><li>当进行 Minor GC 时，将 Eden 区存活的对象被移动到 to 区，并且当前对象的年龄会加 1，清空 Eden 区，再将 from 和 to 两个区域互换</li><li>当再一次触发 Minor GC 的时候，会把 Eden 区中存活下来的对象和 from 中的对象，移动到 to 区中，这些对象的年龄会加 1，清空 Eden 区和 from 区，再将 from 和 to 两个区域互换</li><li>To 区永远是空 Survivor 区，From 区是有数据的，每次 MinorGC 后两个区域互换</li><li>From 区和 To 区 也可以叫做 S0 区和 S1 区</li></ul><p>晋升到老年代：</p><ul><li><p><strong>大对象直接进入老年代</strong>：大对象就是需要大量连续内存空间的对象（比如：字符串、数组）。为了避免在 Eden 和 Survivor 之间的大量复制而降低效率。</p><p><code>-XX:PretenureSizeThreshold</code>：大于此值的对象直接在老年代分配</p></li><li><p><strong>长期存活的对象进入老年代</strong>：为对象定义年龄计数器，对象在 Eden 出生并经过 Minor GC 依然存活，将移动到 Survivor 中，年龄就增加 1 岁，增加到一定年龄则移动到老年代中</p><p><code>-XX:MaxTenuringThreshold</code>：定义年龄的阈值，对象头中用 4 个 bit 存储，所以最大值是 15，默认也是 15</p></li><li><p><strong>动态对象年龄判定</strong>：Hotspot 遍历所有对象时，按照年龄从小到大对其所占用的大小进行累加，当累加到某个年龄时，所占用大小超过了 Survivor 空间的 50% 时，则大于等于该年龄的对象就可以直接进入老年代，无须等到<code>MaxTenuringThreshold</code>中要求的年龄。</p><p><code>-XX:TargetSurvivorRatio=percent</code> ：设定survivor区的目标使用率，默认值是 50%</p><p>取这个年龄和 <code>MaxTenuringThreshold</code> 中更小的一个值，作为新的晋升年龄的阈值</p></li></ul><p>空间分配担保：</p><ul><li>在发生 Minor GC 之前，虚拟机先检查老年代最大可用的连续空间是否大于新生代所有对象总空间，如果条件成立的话，那么 Minor GC 可以确认是安全的</li><li>如果不成立，虚拟机会查看 HandlePromotionFailure 的值是否允许担保失败，如果允许那么就会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小，如果大于将尝试着进行一次 Minor GC；如果小于或者 HandlePromotionFailure 的值不允许冒险，那么就要进行一次 Full GC</li></ul><h2 id="❸回收策略"><a href="#❸回收策略" class="headerlink" title="❸回收策略"></a>❸回收策略</h2><h3 id="1-触发条件"><a href="#1-触发条件" class="headerlink" title="1.触发条件"></a>1.触发条件</h3><p>内存垃圾回收机制主要集中的区域就是线程共享区域：<strong>堆和方法区</strong></p><p>Minor GC 触发条件：当 Eden 空间满时，就将触发一次 Minor GC</p><p>Full GC 同时回收新生代、老年代和方法区，有以下触发条件：</p><ul><li><p>调用 System.gc()：</p><ul><li>在默认情况下，通过 System.gc() 或 Runtime.getRuntime().gc() 的调用，会显式触发 FullGC，同时对老年代和新生代进行回收，但是虚拟机不一定真正去执行，无法保证对垃圾收集器的调用</li><li>不建议使用这种方式，应该让虚拟机管理内存。一般情况下，垃圾回收应该是自动进行的，无须手动触发；在一些特殊情况下，如正在编写一个性能基准，可以在运行之间调用 System.gc()</li></ul></li><li><p>老年代空间不足：</p><ul><li>为了避免引起的 Full GC，应当尽量不要创建过大的对象以及数组</li><li>通过 -Xmn 参数调整新生代的大小，让对象尽量在新生代被回收掉不进入老年代，可以通过 <code>-XX:MaxTenuringThreshold</code> 调大对象进入老年代的年龄，让对象在新生代多存活一段时间</li></ul></li><li><p>空间分配担保失败</p></li><li><p>JDK 1.7 及以前的永久代（方法区）空间不足</p></li><li><p>Concurrent Mode Failure：执行 CMS GC 的过程中同时有对象要放入老年代，而此时老年代空间不足（可能是 GC 过程中浮动垃圾过多导致暂时性的空间不足），便会报 Concurrent Mode Failure 错误，并触发 Full GC</p></li></ul><p>手动 GC 测试，VM参数：<code>-XX:+PrintGcDetails</code></p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">localvarGC1</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">byte</span><span class="token punctuation">[</span><span class="token punctuation">]</span> buffer <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">byte</span><span class="token punctuation">[</span><span class="token number">10</span> <span class="token operator">*</span> <span class="token number">1024</span> <span class="token operator">*</span> <span class="token number">1024</span><span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//10MB</span>    System<span class="token punctuation">.</span><span class="token function">gc</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//输出: 不会被回收, FullGC时被放入老年代</span><span class="token punctuation">}</span><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">localvarGC2</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">byte</span><span class="token punctuation">[</span><span class="token punctuation">]</span> buffer <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">byte</span><span class="token punctuation">[</span><span class="token number">10</span> <span class="token operator">*</span> <span class="token number">1024</span> <span class="token operator">*</span> <span class="token number">1024</span><span class="token punctuation">]</span><span class="token punctuation">;</span>    buffer <span class="token operator">=</span> null<span class="token punctuation">;</span>    System<span class="token punctuation">.</span><span class="token function">gc</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//输出: 正常被回收</span><span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">localvarGC3</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>     <span class="token punctuation">{</span>         <span class="token keyword">byte</span><span class="token punctuation">[</span><span class="token punctuation">]</span> buffer <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">byte</span><span class="token punctuation">[</span><span class="token number">10</span> <span class="token operator">*</span> <span class="token number">1024</span> <span class="token operator">*</span> <span class="token number">1024</span><span class="token punctuation">]</span><span class="token punctuation">;</span>     <span class="token punctuation">}</span>     System<span class="token punctuation">.</span><span class="token function">gc</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//输出: 不会被回收, FullGC时被放入老年代</span> <span class="token punctuation">}</span><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">localvarGC4</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token punctuation">{</span>        <span class="token keyword">byte</span><span class="token punctuation">[</span><span class="token punctuation">]</span> buffer <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">byte</span><span class="token punctuation">[</span><span class="token number">10</span> <span class="token operator">*</span> <span class="token number">1024</span> <span class="token operator">*</span> <span class="token number">1024</span><span class="token punctuation">]</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">int</span> value <span class="token operator">=</span> <span class="token number">10</span><span class="token punctuation">;</span>    System<span class="token punctuation">.</span><span class="token function">gc</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//输出: 正常被回收，slot复用，局部变量过了其作用域 buffer置空</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="2-安全区域"><a href="#2-安全区域" class="headerlink" title="2.安全区域"></a>2.安全区域</h3><p>安全点 (Safepoint)：程序执行时并非在所有地方都能停顿下来开始 GC，只有在安全点才能停下</p><ul><li>Safe Point 的选择很重要，如果太少可能导致 GC 等待的时间太长，如果太多可能导致运行时的性能问题</li><li>大部分指令的执行时间都非常短，通常会根据是否具有让程序长时间执行的特征为标准，选择些执行时间较长的指令作为 Safe Point， 如方法调用、循环跳转和异常跳转等</li></ul><p>在 GC 发生时，让所有线程都在最近的安全点停顿下来的方法：</p><ul><li>抢先式中断：没有虚拟机采用，首先中断所有线程，如有线程不在安全点，就恢复线程让线程运行到安全点</li><li>主动式中断：设置一个中断标志，各个线程运行到各个 Safe Point 时就轮询这个标志，如果中断标志为真，则将自己进行中断挂起</li></ul><p>问题：Safepoint 保证程序执行时，在不太长的时间内就会遇到可进入 GC 的 Safepoint，但是当线程处于 Waiting 状态或 Blocked 状态，线程无法响应 JVM 的中断请求，运行到安全点去中断挂起，JVM 也不可能等待线程被唤醒，对于这种情况，需要安全区域来解决</p><p>安全区域 (Safe Region)：指在一段代码片段中，<strong>对象的引用关系不会发生变化</strong>，在这个区域中的任何位置开始 GC 都是安全的</p><p>运行流程：</p><ul><li><p>当线程运行到 Safe Region 的代码时，首先标识已经进入了 Safe Region，如果这段时间内发生 GC，JVM 会忽略标识为 Safe Region 状态的线程</p></li><li><p>当线程即将离开 Safe Region 时，会检查 JVM 是否已经完成 GC，如果完成了则继续运行，否则线程必须等待 GC 完成，收到可以安全离开 Safe Region 的信号</p></li></ul><h3 id="3-GC分类"><a href="#3-GC分类" class="headerlink" title="3.GC分类"></a>3.GC分类</h3><ul><li>Minor GC：回收新生代，新生代对象存活时间很短，所以 Minor GC 会频繁执行，执行的速度比较快</li><li>Major GC：回收老年代。目前只有CMS收集器会有单独收集老年代的行为。</li><li>Mixed GC：回收整个新生代以及部分老年代的垃圾收集。目前只有G1收集器会有这种行为。</li><li>Full GC：回收整个Java堆和方法区。老年代对象其存活时间长，所以 Full GC 很少执行，执行速度会比 Minor GC 慢很多</li></ul><h2 id="❹垃圾判断"><a href="#❹垃圾判断" class="headerlink" title="❹垃圾判断"></a>❹垃圾判断</h2><h3 id="1-垃圾介绍"><a href="#1-垃圾介绍" class="headerlink" title="1.垃圾介绍"></a>1.垃圾介绍</h3><p>垃圾：<strong>如果一个或多个对象没有任何的引用指向它了，那么这个对象现在就是垃圾</strong></p><p>作用：释放没用的对象，清除内存里的记录碎片，碎片整理将所占用的堆内存移到堆的一端，以便 JVM 将整理出的内存分配给新的对象</p><p>区域：垃圾收集主要是针对<strong>堆</strong>和<strong>方法区</strong>进行，程序计数器、虚拟机栈和本地方法栈这三个区域属于线程私有的，只存在于线程的生命周期内，线程结束之后就会消失，因此不需要对这三个区域进行垃圾回收</p><p>在堆里存放着几乎所有的 Java 对象实例，在 GC 执行垃圾回收之前，首先需要区分出内存中哪些是存活对象，哪些是已经死亡的对象。只有被标记为己经死亡的对象，GC 才会在执行垃圾回收时，释放掉其所占用的内存空间，因此这个过程可以称为垃圾标记阶段，判断对象存活一般有两种方式：<strong>引用计数算法</strong>和<strong>可达性分析算法</strong></p><h3 id="2-引用计数法"><a href="#2-引用计数法" class="headerlink" title="2.引用计数法"></a>2.引用计数法</h3><p>引用计数算法（Reference Counting）：对每个对象保存一个整型的引用计数器属性，用于记录对象被引用的情况。对于一个对象 A，只要有任何一个对象引用了 A，则 A 的引用计数器就加 1；当引用失效时，引用计数器就减 1；当对象 A 的引用计数器的值为 0，即表示对象A不可能再被使用，可进行回收（Java 没有采用）</p><p>优点：</p><ul><li>回收没有延迟性，无需等到内存不够的时候才开始回收，运行时根据对象计数器是否为 0，可以直接回收</li><li>在垃圾回收过程中，应用无需挂起；如果申请内存时，内存不足，则立刻报 OOM 错误</li><li>区域性，更新对象的计数器时，只是影响到该对象，不会扫描全部对象</li></ul><p>缺点：</p><ul><li><p>每次对象被引用时，都需要去更新计数器，有一点时间开销</p></li><li><p>浪费 CPU 资源，即使内存够用，仍然在运行时进行计数器的统计。</p></li><li><p><strong>无法解决循环引用问题，会引发内存泄露</strong>（最大的缺点）</p></li></ul><h3 id="3-可达性分析"><a href="#3-可达性分析" class="headerlink" title="3.可达性分析"></a>3.可达性分析</h3><blockquote><p>可达性分析算法：也可以称为根搜索算法、追踪性垃圾收集</p></blockquote><p>GC Roots ：<strong>GC Roots 是一组活跃的引用，不是对象</strong>，放在 GC Roots Set 集合</p><ul><li>虚拟机栈中局部变量表中引用的对象：各个线程被调用的方法中使用到的参数、局部变量等</li><li>本地方法栈中引用的对象</li><li>堆中类静态属性引用的对象</li><li>方法区中的常量引用的对象</li><li>字符串常量池（string Table）里的引用</li><li>同步锁 synchronized 持有的对象</li></ul><p><strong>工作原理</strong></p><p>可达性分析算法以 GC Roots 为起始点，从上至下的方式搜索被 GC Roots  所连接的目标对象</p><ul><li><p>可达性分析算法后，内存中的存活对象都会被 GC Roots 直接或间接连接着，搜索走过的路径称为引用链</p></li><li><p>如果目标对象没有任何引用链相连，则是不可达的，就意味着该对象己经死亡，可以标记为垃圾对象</p></li><li><p>在可达性分析算法中，只有能够被 GC Roots 直接或者间接连接的对象才是存活对象</p></li></ul><p>分析工作必须在一个保障<strong>一致性的快照</strong>中进行，否则结果的准确性无法保证，这也是导致 GC 进行时必须 <code>Stop The World</code> 的一个原因</p><h3 id="4-引用分析"><a href="#4-引用分析" class="headerlink" title="4.引用分析"></a>4.引用分析</h3><p>无论是通过引用计数算法判断对象的引用数量，还是通过可达性分析算法判断对象是否可达，判定对象是否可被回收都与引用有关，Java 提供了四种强度不同的引用类型</p><ul><li><p>1.<strong>强引用</strong>：被强引用关联的对象不会被回收，只有当所有 GC Roots 都不通过【强引用】引用该对象，才能被垃圾回收</p><ul><li><p>强引用可以直接访问目标对象</p></li><li><p>虚拟机宁愿抛出 OOM 异常，也不会回收强引用所指向对象</p></li><li><p>强引用可能导致<strong>内存泄漏</strong></p></li></ul><pre class="line-numbers language-java"><code class="language-java">Object obj <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Object</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//使用 new 一个新对象的方式来创建强引用</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre></li><li><p>2.<strong>软引用（SoftReference）</strong>：被软引用关联的对象只有在内存不够的情况下才会被回收</p><ul><li><p><strong>仅（可能有强引用，一个对象可以被多个引用）</strong>有软引用引用该对象时，在垃圾回收后，内存仍不足时会再次出发垃圾回收，回收软引用对象</p><ul><li><p>配合<strong>引用队列来释放软引用自身</strong>，在构造软引用时，可以指定一个引用队列，当软引用对象被回收时，就会加入指定的引用队列，通过这个队列可以跟踪对象的回收情况</p></li><li><p>软引用通常用来实现内存敏感的缓存，比如高速缓存就有用到软引用；如果还有空闲内存，就可以暂时保留缓存，当内存不足时清理掉，这样就保证了使用缓存的同时不会耗尽内存</p></li></ul><pre class="line-numbers language-java"><code class="language-java">Object obj <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Object</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>SoftReference<span class="token operator">&lt;</span>Object<span class="token operator">></span> sf <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">SoftReference</span><span class="token operator">&lt;</span>Object<span class="token operator">></span><span class="token punctuation">(</span>obj<span class="token punctuation">)</span><span class="token punctuation">;</span>obj <span class="token operator">=</span> null<span class="token punctuation">;</span>  <span class="token comment" spellcheck="true">// 使对象只被软引用关联</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre></li></ul></li><li><p>3.<strong>弱引用（WeakReference）</strong>：被弱引用关联的对象一定会被回收，只能存活到下一次垃圾回收发生之前</p><ul><li><p>仅有弱引用引用该对象时，在垃圾回收时，无论内存是否充足，都会回收弱引用对象</p><ul><li><p>配合引用队列来释放弱引用自身</p></li><li><p>WeakHashMap 用来存储图片信息，可以在内存不足的时候及时回收，避免了 OOM</p></li></ul><pre class="line-numbers language-java"><code class="language-java">Object obj <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Object</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>WeakReference<span class="token operator">&lt;</span>Object<span class="token operator">></span> wf <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">WeakReference</span><span class="token operator">&lt;</span>Object<span class="token operator">></span><span class="token punctuation">(</span>obj<span class="token punctuation">)</span><span class="token punctuation">;</span>obj <span class="token operator">=</span> null<span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre></li></ul></li><li><p>4.<strong>虚引用（PhantomReference）</strong>：也称为幽灵引用或者幻影引用，是所有引用类型中最弱的一个</p><ul><li>必须配合引用队列使用，主要配合 ByteBuffer 使用，被引用对象回收时会将虚引用入队，由 Reference Handler 线程调用虚引用相关方法释放直接内存</li></ul><ul><li><p>一个对象是否有虚引用的存在，不会对其生存时间造成影响，也无法通过虚引用得到一个对象</p><ul><li>为对象设置虚引用的唯一目的是在于跟踪垃圾回收过程，能在这个对象被回收时收到一个系统通知</li></ul></li></ul><pre class="line-numbers language-java"><code class="language-java">Object obj <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Object</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>PhantomReference<span class="token operator">&lt;</span>Object<span class="token operator">></span> pf <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">PhantomReference</span><span class="token operator">&lt;</span>Object<span class="token operator">></span><span class="token punctuation">(</span>obj<span class="token punctuation">,</span> null<span class="token punctuation">)</span><span class="token punctuation">;</span>obj <span class="token operator">=</span> null<span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre></li><li><p>5.<strong>终结器引用（finalization）</strong></p><ul><li>无需手动编码，但其内部配合引用队列使用，在垃圾回收时，终结器引用入队（被引用对象暂时没有被回收），再由 Finalizer 线程通过终结器引用找到被引用对象并调用它的 finalize 方法，第二次 GC 时才能回收被引用对象</li></ul></li></ul><h3 id="5-三色标记"><a href="#5-三色标记" class="headerlink" title="5.三色标记"></a>5.三色标记</h3><h4 id="基本算法"><a href="#基本算法" class="headerlink" title="基本算法"></a>基本算法</h4><p>三色标记法把遍历对象图过程中遇到的对象，标记成以下三种颜色：</p><ul><li>白色：尚未访问过</li><li>灰色：正在访问的（本对象已访问过，但是本对象引用到的其他对象尚未全部访问）</li><li>黑色：访问完成的（本对象已访问过，而且本对象引用到的其他对象也全部访问完成）</li></ul><p>当 Stop The World (STW) 时，对象间的引用是不会发生变化的，可以轻松完成标记，遍历访问过程为：</p><ol><li><p>初始时，所有对象都在 【白色集合】中；</p></li><li><p>将 GC Roots 直接引用到的对象挪到 【灰色集合】中；</p></li><li><p>从灰色集合中获取对象：</p><ul><li>将本对象引用到的其他对象全部挪到 【灰色集合】中；</li><li>将本对象挪到 【黑色集合】里面。</li></ul></li><li><p>重复步骤3，直至【灰色集合】为空时结束。</p></li><li><p>结束后，仍在【白色集合】的对象即为 GC Roots 不可达，可以进行回收。</p></li></ol><img src="https://img.jwt1399.top/img/202212191451907." alt="三色标记遍历过程" style="zoom:67%;" /><p>并发标记时，对象间的引用可能发生变化，多标和漏标的情况就有可能发生</p><h4 id="多标-浮动垃圾"><a href="#多标-浮动垃圾" class="headerlink" title="多标-浮动垃圾"></a>多标-浮动垃圾</h4><p><strong>多标情况：</strong>当 E 变为灰色时，断开 D 对 E 的引用，导致对象 E&#x2F;F&#x2F;G 仍会被标记为存活，本轮 GC 不会回收这部分内存，这部分本应该回收但是没有回收到的内存，被称之为<strong>浮动垃圾</strong></p><ul><li>针对并发标记开始后的<strong>新对象</strong>，通常的做法是直接全部当成黑色，也算浮动垃圾</li><li>浮动垃圾并不会影响应用程序的正确性，只是需要等到下一轮垃圾回收中才被清除</li></ul><img src="https://img.jwt1399.top/img/202212191503059.png" style="zoom: 50%;" /><h4 id="漏标-读写屏障"><a href="#漏标-读写屏障" class="headerlink" title="漏标-读写屏障"></a>漏标-读写屏障</h4><p><strong>漏标情况：</strong>当 E 变为灰色时，断开 E 对 G 的引用，再让 D 引用 G。此时切回 GC 线程继续跑，因为 E 已经没有对 G 的引用了，所以不会将 G 放到灰色集合；尽管 D 重新引用了G，但 D 已经是黑色了，不会再重新做遍历处理。<br> 最终导致的结果是：G 会一直停留在白色集合中，<strong>最后被当作垃圾进行清除</strong>。这直接<strong>影响到了应用程序的正确性</strong>，是不可接受的。</p><img src="https://img.jwt1399.top/img/202212191513939.png" style="zoom:50%;" /><p>即漏标只有<strong>同时满足</strong>以下两个条件时才会发生：</p><ul><li>条件一：灰色对象断开了对一个白色对象的引用（直接或间接），即灰色对象原成员变量的引用发生了变化</li><li>条件二：其他线程中修改了黑色对象，插入了一条或多条对该白色对象的新引用</li><li>结果：导致该白色对象当作垃圾被 GC，影响到了程序的正确性</li></ul><p>代码角度解释漏标：</p><pre class="line-numbers language-java"><code class="language-java">var G <span class="token operator">=</span> objE<span class="token punctuation">.</span>fieldG<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 1.读</span>objE<span class="token punctuation">.</span>fieldG <span class="token operator">=</span> null<span class="token punctuation">;</span>  <span class="token comment" spellcheck="true">// 2.写</span>objD<span class="token punctuation">.</span>fieldG <span class="token operator">=</span> G<span class="token punctuation">;</span>     <span class="token comment" spellcheck="true">// 3.写</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><ol><li><strong>读取</strong> 对象E的成员变量fieldG的引用值，即对象G；</li><li>对象E 往其成员变量fieldG，<strong>写入</strong> null值。</li><li>对象D 往其成员变量fieldG，<strong>写入</strong> 对象G ；</li></ol><p>为了解决问题，我们只要在上面这三步中的任意一步中做一些“手脚”，<strong>将对象G记录起来，然后作为灰色对象再进行遍历</strong>即可。比如放到一个特定的集合，等初始的GC Roots遍历完（并发标记），再遍历该集合（重新标记）。</p><blockquote><p><strong>重新标记通常是需要STW的</strong>，因为应用程序一直在跑的话，该集合可能会一直增加新的对象，导致永远都跑不完。当然，并发标记期间也可以将该集合中的大部分先跑了，从而缩短重新标记STW的时间，这个是优化问题了。</p></blockquote><p>解决方法：添加读写屏障，读屏障拦截第一步，写屏障拦截第二三步，在读写前后进行一些后置处理：</p><ul><li><p><strong>写屏障 + 增量更新</strong>：黑色对象新增引用，会将黑色对象变成灰色对象，最后对该节点重新扫描，增量更新 (Incremental Update) 破坏了条件二，从而保证了不会漏标</p><p>缺点：对黑色变灰的对象重新扫描所有引用，比较耗费时间</p></li><li><p><strong>写屏障 (Store Barrier) + SATB</strong>：当原来成员变量的引用发生变化之前，记录下原来的引用对象</p><p>保留 GC 开始时的对象图，即原始快照 SATB，当 GC Roots 确定后，对象图就已经确定，那后续的标记也应该是按照这个时刻的对象图走，如果期间对白色对象有了新的引用会记录下来，并且将白色对象变灰（说明可达了，并且原始快照中本来就应该是灰色对象），最后重新扫描该对象的引用关系</p><p>SATB (Snapshot At The Beginning) 破坏了条件一，从而保证了不会漏标</p></li><li><p>**读屏障 (Load Barrier)**：破坏条件二，黑色对象引用白色对象的前提是获取到该对象，此时读屏障发挥作用</p></li></ul><p>以 Java HotSpot VM 为例，其并发标记时对漏标的处理方案如下：</p><ul><li>CMS：写屏障 + 增量更新</li><li>G1：写屏障 + SATB</li><li>ZGC：读屏障</li></ul><h3 id="6-finalization"><a href="#6-finalization" class="headerlink" title="6.finalization"></a>6.finalization</h3><p>Java 语言提供了对象终止（finalization）机制来允许开发人员提供对象被销毁之前的自定义处理逻辑</p><p>垃圾回收对象之前，会先调用这个对象的 <code>finalize()</code> 方法，finalize() 方法允许在子类中被重写，用于在对象被回收时进行后置处理，通常在这个方法中进行一些资源释放和清理，比如关闭文件、套接字和数据库连接等</p><p>生存 OR 死亡：如果从所有的根节点都无法访问到某个对象，说明对象己经不再使用，此对象需要被回收。但事实上这时候它们暂时处于缓刑阶段。<strong>一个无法触及的对象有可能在某个条件下复活自己</strong>，所以虚拟机中的对象可能的三种状态：</p><ul><li>可触及的：从根节点开始，可以到达这个对象</li><li>可复活的：对象的所有引用都被释放，但是对象有可能在 finalize() 中复活</li><li>不可触及的：对象的 finalize() 被调用并且没有复活，那么就会进入不可触及状态，不可触及的对象不可能被复活，因为 <strong>finalize() 只会被调用一次</strong>，等到这个对象再被标记为可回收时就必须回收</li></ul><p>永远不要主动调用某个对象的 finalize() 方法，应该交给垃圾回收机制调用，原因：</p><ul><li>finalize() 时可能会导致对象复活</li><li>finalize() 方法的执行时间是没有保障的，完全由 GC 线程决定，极端情况下，若不发生 GC，则 finalize() 方法将没有执行机会，因为优先级比较低，即使主动调用该方法，也不会因此就直接进行回收</li><li>一个糟糕的 finalize() 会严重影响 GC 的性能</li></ul><h3 id="7-无用属性"><a href="#7-无用属性" class="headerlink" title="7.无用属性"></a>7.无用属性</h3><h4 id="无用类"><a href="#无用类" class="headerlink" title="无用类"></a>无用类</h4><p>方法区主要回收的是无用的类</p><p>判定一个类是否是无用的类，需要同时满足下面 3 个条件：</p><ul><li>该类所有的实例都已经被回收，也就是 Java 堆中不存在该类的任何实例</li><li>加载该类的 <code>ClassLoader</code> 已经被回收</li><li>该类对应的 <code>java.lang.Class</code> 对象没有在任何地方被引用，无法在任何地方通过反射访问该类的方法</li></ul><p>虚拟机可以对满足上述 3 个条件的无用类进行回收，这里说的<strong>仅仅是可以</strong>，而并不是和对象一样不使用了就会必然被回收</p><h4 id="废弃常量"><a href="#废弃常量" class="headerlink" title="废弃常量"></a>废弃常量</h4><p>在常量池中存在字符串 “abc”，如果当前没有任何 String 对象引用该常量，说明常量 “abc” 是废弃常量，如果这时发生内存回收的话<strong>而且有必要的话</strong>（内存不够用），”abc” 就会被系统清理出常量池</p><h4 id="静态变量"><a href="#静态变量" class="headerlink" title="静态变量"></a>静态变量</h4><p>类加载时（第一次访问），这个类中所有静态成员就会被加载到静态变量区，该区域的成员一旦创建，直到程序退出才会被回收</p><p>如果是静态引用类型的变量，静态变量区只存储一份对象的引用地址，真正的对象在堆内，如果要回收该对象可以设置引用为 null</p><h2 id="❺回收算法"><a href="#❺回收算法" class="headerlink" title="❺回收算法"></a>❺回收算法</h2><h3 id="1-标记复制"><a href="#1-标记复制" class="headerlink" title="1.标记复制"></a>1.标记复制</h3><p>复制算法的核心就是，<strong>将原有的内存空间一分为二，每次只用其中的一块</strong>，在垃圾回收时，将正在使用的对象复制到另一个内存空间中，然后将该内存空间清理，交换两个内存的角色，完成垃圾的回收</p><p><img src="https://img.jwt1399.top/img/202212181546444.png"></p><p>算法优点：</p><ul><li>没有标记和清除过程，实现简单，运行速度快</li><li>复制过去以后保证空间的连续性，不会出现碎片问题</li></ul><p>算法缺点：</p><ul><li>主要不足是<strong>只使用了内存的一半</strong></li><li>对于 G1 这种分拆成为大量 region 的 GC，复制而不是移动，意味着 GC 需要维护 region 之间对象引用关系，不管是内存占用或者时间开销都不小</li></ul><p>基于新生代 “朝生夕灭” 的特点，大多数虚拟机都不会按照 1:1 的比例来进行内存划分，例如 HotSpot 虚拟机会将内存空间划分为一块较大的 <code>Eden</code> 和 两块较小的 <code>Survivor</code> 空间，它们之间的比例是 8:1:1 。 每次分配时只会使用 <code>Eden</code> 和其中的一块 <code>Survivor</code> ，发生垃圾回收时，只需要将存活的对象一次性复制到另外一块 <code>Survivor</code> 上，这样只有 10% 的内存空间会被浪费掉。当 <code>Survivor</code> 空间不足以容纳一次 <code>Minor GC</code> 时，此时由其他内存区域（通常是老年代）来进行分配担保。</p><p>应用场景：如果内存中的垃圾对象较多，需要复制的对象就较少，这种情况下适合使用该方式并且效率比较高，反之则不适合</p><p>现在的商业虚拟机都采用这种收集算法<strong>回收新生代</strong>，因为新生代 GC 频繁并且对象的存活率不高，但是并不是划分为大小相等的两块，而是一块较大的 Eden 空间和两块较小的 Survivor 空间</p><h3 id="2-标记清除"><a href="#2-标记清除" class="headerlink" title="2.标记清除"></a>2.标记清除</h3><p>标记清除算法，是将垃圾回收分为两个阶段，分别是<strong>标记和清除</strong></p><ul><li><p><strong>标记</strong>：Collector 从引用根节点开始遍历，标记所有被引用的对象，一般是在对象的 Header 中记录为可达对象，<strong>标记的是引用的对象，不是垃圾</strong></p></li><li><p><strong>清除</strong>：Collector 对堆内存从头到尾进行线性的遍历，如果发现某个对象在其 Header 中没有标记为可达对象，则将其回收，把分块连接到<strong>空闲列表</strong>的单向链表，判断回收后的分块与前一个空闲分块是否连续，若连续会合并这两个分块，之后进行分配时只需要遍历这个空闲列表，就可以找到分块</p></li><li><p><strong>分配阶段</strong>：程序会搜索空闲链表寻找空间大于等于新对象大小 size 的块 block，如果找到的块等于 size，会直接返回这个分块；如果找到的块大于 size，会将块分割成大小为 size 与 block - size 的两部分，返回大小为 size 的分块，并把大小为 block - size 的块返回给空闲列表</p></li></ul><p><img src="https://img.jwt1399.top/img/202212181546206.png"></p><p>算法缺点：</p><ul><li>标记和清除过程效率都不高</li><li>会产生大量不连续的内存碎片，导致无法给大对象分配内存，需要维护一个空闲链表</li></ul><p>算法优点：</p><ul><li>速度较快</li></ul><h3 id="3-标记整理"><a href="#3-标记整理" class="headerlink" title="3.标记整理"></a>3.标记整理</h3><p>标记整理（压缩）算法是在标记清除算法的基础之上，做了优化改进的算法</p><ul><li>标记阶段和标记清除算法一样，也是从根节点开始，将对象的引用进行标记</li><li>清理阶段，并不是简单的直接清理可回收对象，而是<strong>将存活对象都向内存另一端移动</strong>，然后清理边界以外的垃圾，从而<strong>解决了碎片化</strong>的问题</li></ul><p><img src="https://img.jwt1399.top/img/202212181547195.png"></p><p>优点：不会产生内存碎片</p><p>缺点：需要移动大量对象，处理效率比较低</p><h3 id="4-对比总结"><a href="#4-对比总结" class="headerlink" title="4.对比总结"></a>4.对比总结</h3><table><thead><tr><th align="center">算法</th><th align="center">速度</th><th align="center">空间开销</th><th align="center">移动对象</th></tr></thead><tbody><tr><td align="center"><strong>复制算法</strong></td><td align="center">最快</td><td align="center">通常需要活对象的 2 倍大小（不堆积碎片）</td><td align="center">是</td></tr><tr><td align="center"><strong>标记清除</strong></td><td align="center">中等</td><td align="center">少（但会堆积碎片）</td><td align="center">否</td></tr><tr><td align="center"><strong>标记整理</strong></td><td align="center">最慢</td><td align="center">少（不堆积碎片）</td><td align="center">是</td></tr></tbody></table><h2 id="❻垃圾回收器"><a href="#❻垃圾回收器" class="headerlink" title="❻垃圾回收器"></a>❻垃圾回收器</h2><h3 id="0-概述"><a href="#0-概述" class="headerlink" title="0.概述"></a>0.概述</h3><h4 id="a-垃圾收集器分类"><a href="#a-垃圾收集器分类" class="headerlink" title="a.垃圾收集器分类"></a>a.垃圾收集器分类</h4><ul><li>按线程数分（垃圾回收线程数），可以分为<strong>串行垃圾回收器</strong>和<strong>并行垃圾回收器</strong><ul><li>除了 CMS 和 G1 之外，其它垃圾收集器都是以串行(并发也是串行)的方式执行</li></ul></li><li>按照工作模式分，可以分为<strong>并发式垃圾回收器</strong>和<strong>独占式垃圾回收器</strong><ul><li>并发式垃圾回收器与应用程序线程交替工作，以尽可能减少应用程序的停顿时间</li><li>独占式垃圾回收器（Stop the world）一旦运行，就停止应用程序中的所有用户线程，直到垃圾回收过程完全结束</li></ul></li><li>按碎片处理方式分，可分为<strong>压缩式垃圾回收器</strong>和<strong>非压缩式垃圾回收器</strong><ul><li>压缩式垃圾回收器在回收完成后进行压缩整理，消除回收后的碎片，再分配对象空间使用指针碰撞</li><li>非压缩式的垃圾回收器不进行这步操作，再分配对象空间使用空闲列表</li></ul></li><li>按工作的内存区间分，又可分为<strong>年轻代垃圾回收器</strong>和<strong>老年代垃圾回收器</strong></li></ul><h4 id="b-GC-性能指标"><a href="#b-GC-性能指标" class="headerlink" title="b.GC 性能指标"></a>b.GC 性能指标</h4><ul><li>吞吐量：程序的运行时间占总运行时间的比例（总运行时间 &#x3D; 程序的运行时间 + 内存回收的时间）</li><li>垃圾收集开销：吞吐量的补数，垃圾收集所用时间与总运行时间的比例</li><li>暂停时间：执行垃圾收集时，程序的工作线程被暂停的时间</li><li>收集频率：相对于应用程序的执行，收集操作发生的频率</li><li>内存占用：Java 堆区所占的内存大小</li><li>快速：一个对象从诞生到被回收所经历的时间</li><li>吞吐量优先：单位时间内，STW（stop the world，停掉其他所有工作线程）时间最短</li><li>响应时间优先：尽可能让单次STW时间变短（尽量不影响其他线程运行）</li></ul><h4 id="c-垃圾收集器的组合关系"><a href="#c-垃圾收集器的组合关系" class="headerlink" title="c.垃圾收集器的组合关系"></a>c.垃圾收集器的组合关系</h4><ul><li><p>新生代收集器：Serial、ParNew、Parallel Scavenge</p></li><li><p>老年代收集器：Serial old、Parallel old、CMS</p></li><li><p>整堆收集器：G1</p></li></ul><p><img src="https://img.jwt1399.top/img/202212191543682.png">红色虚线在 JDK9 移除、绿色虚线在 JDK14 弃用该组合、青色虚线在 JDK14 删除 CMS 垃圾回收器</p><p>Serial GC、Parallel GC、Concurrent Mark Sweep GC 这三个 GC  不同：</p><ul><li>最小化地使用内存和并行开销，选 Serial GC</li><li>最大化应用程序的吞吐量，选 Parallel GC</li><li>最小化 GC 的中断或停顿时间，选 CMS GC</li></ul><p><img src="https://img.jwt1399.top/img/202212181620954.png"></p><p>查看默认的垃圾收回收器：</p><ul><li><p><code>-XX:+PrintcommandLineFlags</code>：查看命令行相关参数（包含使用的垃圾收集器）</p></li><li><p>使用命令行指令：jinfo -flag 相关垃圾回收器参数  进程 ID</p></li></ul><h3 id="1-Serial-x2F-Serial-old"><a href="#1-Serial-x2F-Serial-old" class="headerlink" title="1.Serial&#x2F;Serial old"></a>1.Serial&#x2F;Serial old</h3><p><strong>Serial</strong>：串行垃圾收集器，作用于新生代，使用单线程进行垃圾回收，采用<strong>复制算法</strong>，新生代基本都是复制算法</p><p><strong>STW（Stop-The-World）</strong>：垃圾回收时，只有一个线程在工作，并且 Java 应用中的所有线程都要暂停，等待垃圾回收的完成</p><p><strong>Serial old</strong>：执行老年代垃圾回收的串行收集器，内存回收算法使用的是<strong>标记-整理算法</strong>，同样也采用了串行回收和 STW 机制</p><ul><li>Serial old 是 Client 模式下默认的老年代的垃圾回收器</li><li>Serial old 在 Server 模式下主要有两个用途：<ul><li>在 JDK 1.5 以及之前版本（Parallel Old 诞生以前）中与 Parallel Scavenge 收集器搭配使用</li><li>作为老年代 CMS 收集器的<strong>后备垃圾回收方案</strong>，在并发收集发生 Concurrent Mode Failure 时使用</li></ul></li></ul><p>开启参数：<code>-XX:+UseSerialGC</code> 等价于新生代用 Serial GC 且老年代用 Serial old GC</p><p><img src="https://img.jwt1399.top/img/202212191545395.png"></p><p>优点：简单而高效（与其他收集器的单线程比），对于限定单个 CPU 的环境来说，Serial 收集器由于没有线程交互的开销，可以获得最高的单线程收集效率</p><p>缺点：对于交互性较强的应用而言，这种垃圾收集器是不能够接受的，比如 JavaWeb 应用</p><h3 id="2-ParNew"><a href="#2-ParNew" class="headerlink" title="2.ParNew"></a>2.ParNew</h3><p>Par 是 Parallel 并行的缩写，New 是只能处理的是新生代</p><p>并行垃圾收集器在串行垃圾收集器的基础之上做了改进，<strong>采用复制算法</strong>，将单线程改为了多线程进行垃圾回收，可以缩短垃圾回收的时间</p><p>对于其他的行为（收集算法、stop the world、对象分配规则、回收策略等）同 Serial 收集器一样，应用在年轻代，除 Serial 外，只有<strong>ParNew GC 能与 CMS 收集器配合工作</strong></p><p>相关参数：</p><ul><li><p><code>-XX：+UseParNewGC</code>：表示新生代使用并行收集器，不影响老年代</p></li><li><p><code>-XX:ParallelGCThreads</code>：默认开启和 CPU 数量相同的线程数</p></li></ul><p><img src="https://img.jwt1399.top/img/202212191545235.png"></p><p>ParNew 是很多 JVM 运行在 Server 模式下新生代的默认垃圾收集器</p><ul><li>对于新生代，回收次数频繁，使用并行方式高效</li><li>对于老年代，回收次数少，使用串行方式节省资源（CPU 并行需切换线程，串行可以省去切换线程的资源）</li></ul><h3 id="3-Parallel-x2F-Parallel-Old"><a href="#3-Parallel-x2F-Parallel-Old" class="headerlink" title="3.Parallel&#x2F;Parallel Old"></a>3.Parallel&#x2F;Parallel Old</h3><p>Parallel Scavenge 收集器：是应用于新生代的并行垃圾回收器，<strong>采用复制算法</strong>、并行回收和 Stop the World 机制</p><p>Parallel Old ：是应用于老年代的并行垃圾回收器，<strong>采用标记-整理算法</strong></p><p>对比其他回收器：</p><ul><li>其它收集器目标是尽可能缩短垃圾收集时用户线程的停顿时间</li><li>Parallel 目标是达到一个可控制的吞吐量，被称为<strong>吞吐量优先</strong>收集器</li><li>Parallel Scavenge 对比 ParNew 拥有<strong>自适应调节策略</strong>，可以通过一个开关参数打开 GC Ergonomics</li></ul><p>应用场景：</p><ul><li>停顿时间越短就越适合需要与用户交互的程序，良好的响应速度能提升用户体验</li><li>高吞吐量可以高效率地利用 CPU 时间，尽快完成程序的运算任务，适合在后台运算而不需要太多交互</li></ul><p>停顿时间和吞吐量的关系：新生代空间变小 → 缩短停顿时间 → 垃圾回收变得频繁 → 导致吞吐量下降</p><p>在注重吞吐量及 CPU 资源敏感的场合，都可以优先考虑 Parallel Scavenge + Parallel Old 收集器，在 Server 模式下的内存回收性能很好，<strong>Java8 默认是此垃圾收集器组合</strong></p><p><img src="https://img.jwt1399.top/img/202212191559772.png"></p><p>参数配置：</p><ul><li><code>-XX：+UseParallelGC</code>：手动指定年轻代使用 Paralle 并行收集器执行内存回收任务</li><li><code>-XX：+UseParalleloldcc</code>：手动指定老年代使用并行回收收集器执行内存回收任务<ul><li>上面两个参数，默认开启一个，另一个也会被开启（互相激活），默认 JDK8 是开启的</li></ul></li><li><code>-XX:+UseAdaptivesizepplicy</code>：设置 Parallel Scavenge 收集器具有<strong>自适应调节策略</strong>，在这种模式下，年轻代的大小、Eden 和 Survivor 的比例、晋升老年代的对象年龄等参数会被自动调整，虚拟机会根据当前系统的运行情况收集性能监控信息，动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量</li><li><code>-XX:ParallelGcThreads</code>：设置年轻代并行收集器的线程数，一般与 CPU 数量相等，以避免过多的线程数影响垃圾收集性能<ul><li>在默认情况下，当 CPU 数量小于 8 个，ParallelGcThreads 的值等于 CPU 数量</li><li>当 CPU 数量大于 8 个，ParallelGCThreads 的值等于 3+[5*CPU Count]&#x2F;8]</li></ul></li><li><code>-XX:MaxGCPauseMillis</code>：设置垃圾收集器最大停顿时间（即 STW 的时间），单位是毫秒<ul><li>对于用户来讲，停顿时间越短体验越好；在服务器端，注重高并发，整体的吞吐量</li><li>为了把停顿时间控制在 MaxGCPauseMillis 以内，收集器在工作时会调整 Java 堆大小或其他一些参数</li></ul></li><li><code>-XX:GCTimeRatio</code>：垃圾收集时间占总时间的比例 &#x3D;1&#x2F;(N+1)，用于衡量吞吐量的大小<ul><li>取值范围（0，100）。默认值 99，也就是垃圾回收时间不超过 1</li><li>与 <code>-xx:MaxGCPauseMillis</code> 参数有一定矛盾性，暂停时间越长，Radio 参数就容易超过设定的比例</li></ul></li></ul><h3 id="4-CMS"><a href="#4-CMS" class="headerlink" title="4.CMS"></a>4.CMS</h3><blockquote><p>Concurrent Mark Sweep（CMS），是一款<strong>并发的、使用标记-清除</strong>算法、<strong>响应时间优先</strong>、<strong>针对老年代</strong>的垃圾回收器，其最大特点是<strong>让垃圾收集线程与用户线程同时工作</strong></p></blockquote><p>CMS 收集器的关注点是尽可能缩短垃圾收集时用户线程的停顿时间，停顿时间越短（<strong>低延迟</strong>）越适合与用户交互的程序，良好的响应速度能提升用户体验</p><p>分为以下四个流程：</p><ul><li><strong>初始标记</strong>：仅标记 GC Roots 能直接关联到的对象，速度很快，出现短暂STW</li><li><strong>并发标记</strong>：进行 GC Roots 开始遍历整个对象图，在整个回收过程中耗时最长，不需要 STW，可以与用户线程并发运行</li><li><strong>重新标记</strong>：修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象，比初始标记时间长但远比并发标记时间短，需要 STW（不停顿就会一直变化，采用写屏障 + 增量更新来避免漏标情况）</li><li><strong>并发清除</strong>：清除标记为可以回收的对象，<strong>不需要移动存活对象</strong>，所以这个阶段可以与用户线程同时并发的</li></ul><p><img src="https://img.jwt1399.top/img/202212192100642.png"></p><p>Mark Sweep 会造成内存碎片，不把算法换成 Mark Compact 的原因：Mark Compact 算法会整理内存，导致用户线程使用的<strong>对象的地址改变</strong>，影响用户线程继续执行</p><p>在整个过程中耗时最长的并发标记和并发清除过程中，收集器线程都可以与用户线程一起工作，不需要进行停顿</p><p>优点：并发收集、低延迟</p><p>缺点：</p><ul><li><p>吞吐量降低：在并发阶段虽然不会导致用户停顿，但是会因为占用了一部分线程而导致应用程序变慢，CPU 利用率不够高</p></li><li><p>CMS 收集器<strong>无法处理浮动垃圾</strong>，可能出现 Concurrent Mode Failure 导致另一次 Full GC 的产生</p><p>浮动垃圾是指并发清除阶段由于用户线程继续运行而产生的垃圾（产生了新对象），这部分垃圾只能到下一次 GC 时才能进行回收。由于浮动垃圾的存在，CMS 收集需要预留出一部分内存，不能等待老年代快满的时候再回收。如果预留的内存不够存放浮动垃圾，就会出现 Concurrent Mode Failure，这时虚拟机将临时启用 Serial Old 来替代 CMS，导致很长的停顿时间</p></li><li><p>标记 - 清除算法导致的空间碎片，往往出现老年代空间无法找到足够大连续空间来分配当前对象，不得不提前触发一次 Full GC；为新对象分配内存空间时，将无法使用指针碰撞（Bump the Pointer）技术，而只能够选择空闲列表（Free List）执行内存分配</p></li></ul><p>参数设置：</p><ul><li><p><code>-XX：+UseConcMarkSweepGC</code>：手动指定使用 CMS 收集器执行内存回收任务</p><p>开启该参数后会自动将 <code>-XX:+UseParNewGC</code> 打开，即：ParNew + CMS + Serial old的组合</p></li><li><p><code>-XX:CMSInitiatingoccupanyFraction</code>：设置堆内存使用率的阈值，一旦达到该阈值，便开始进行回收</p><ul><li>JDK5 及以前版本的默认值为 68，即当老年代的空间使用率达到 68% 时，会执行一次CMS回收</li><li>JDK6 及以上版本默认值为 92%</li></ul></li><li><p><code>-XX:+UseCMSCompactAtFullCollection</code>：用于指定在执行完 Full GC 后对内存空间进行压缩整理，以此避免内存碎片的产生，由于内存压缩整理过程无法并发执行，所带来的问题就是停顿时间变得更长</p></li><li><p><code>-XX:CMSFullGCsBeforecompaction</code>：<strong>设置在执行多少次 Full GC 后对内存空间进行压缩整理</strong></p></li><li><p><code>-XX:ParallelCMSThreads</code>：设置 CMS 的线程数量</p><ul><li>CMS 默认启动的线程数是 (ParallelGCThreads+3)&#x2F;4，ParallelGCThreads 是年轻代并行收集器的线程数</li><li>收集线程占用的 CPU 资源多于25%，对用户程序影响可能较大；当 CPU 资源比较紧张时，受到 CMS 收集器线程的影响，应用程序的性能在垃圾回收阶段可能会非常糟糕</li></ul></li></ul><h3 id="5-G1"><a href="#5-G1" class="headerlink" title="5.G1"></a>5.G1</h3><blockquote><p>G1（Garbage-First）是一款面向服务端应用的垃圾收集器，<strong>应用于新生代和老年代</strong>、采用<strong>标记-整理</strong>算法、<strong>响应时间优先</strong>、软实时、低延迟、可设定目标（最大 STW 停顿时间）的垃圾回收器，用于代替 CMS，适用于较大的堆（&gt;4 ~ 6G），在 JDK9 之后默认使用 G1</p><p>JDK 9发布之日，G1宣告取代Parallel Scavenge加Parallel Old组合，成为服务端模式下的默认垃圾收集器，而CMS则沦落至被声明为不推荐使用（Deprecate）的收集器</p><p>应用场景：</p><ul><li>面向服务端应用，针对具有大内存、多处理器的机器</li><li>需要低 GC 延迟，并具有大堆的应用程序提供解决方案</li></ul></blockquote><h4 id="G1-优点"><a href="#G1-优点" class="headerlink" title="G1 优点"></a>G1 优点</h4><ul><li>并发与并行：<ul><li>并行性：G1 在回收期间，可以有多个 GC 线程同时工作，有效利用多核计算能力，此时用户线程 STW</li><li>并发性：G1 拥有与应用程序交替执行的能力，部分工作可以和应用程序同时执行，因此不会在整个回收阶段发生完全阻塞应用程序的情况</li><li>其他的垃圾收集器使用<strong>内置的 JVM 线程</strong>执行 GC 的多线程操作，而 G1 GC 可以采用<strong>应用线程</strong>承担后台运行的 GC 工作，JVM 的 GC 线程处理速度慢时，系统会<strong>调用应用程序线程加速垃圾回收</strong>过程</li></ul></li></ul><ul><li><p>分区算法：</p><ul><li><p>从分代上看，G1  属于分代型垃圾回收器，区分年轻代和老年代，年轻代依然有 Eden 区和 Survivor 区。从堆结构上看，<strong>新生代和老年代不再物理隔离</strong>，不用担心每个代内存是否足够，这种特性有利于程序长时间运行，分配大对象时不会因为无法找到连续内存空间而提前触发下一次 GC</p></li><li><p>G1 把堆划分成多个大小相等的独立区域（<strong>Region</strong>），使得每个小空间可以单独进行垃圾回收</p><ul><li><p>将整个堆划分成约 2048 个大小相同的独立 Region 块，所有 Region 大小相同，在 JVM 生命周期内不会被改变。</p></li><li><p>每个Region的大小可以通过参数<code>-XX：G1HeapRegionSize</code>设定，取值范围为1MB～32MB之间且为 2 的 N 次幂</p></li><li><p>Region中还有一类特殊的 <strong>Humongous 区域</strong>，专门用来存储大对象，本身属于老年代区。G1认为只要大小超过了一个Region容量一半的对象即可判定为大对象。如果一个 H 区装不下一个巨型对象，那么 G1 会寻找连续的 H 分区来存储，为了能找到连续的 H 区，有时候不得不启动 Full GC</p></li><li><p>G1 不会对巨型对象进行拷贝，回收时被优先考虑，G1 会跟踪老年代所有 incoming 引用，这样老年代 incoming 引用为 0 的巨型对象就可以在新生代垃圾回收时处理掉</p></li></ul></li></ul></li></ul><p><img src="https://img.jwt1399.top/img/202212201526217.png" alt="Region 结构图"></p><ul><li><p>空间整合：</p><ul><li>CMS：标记-清除算法、内存碎片、若干次 GC 后进行一次碎片整理</li><li>G1：整体来看是<strong>基于标记 - 整理算法实现</strong>的收集器，从局部（Region 之间）上来看是<strong>基于复制算法实现</strong>的，两种算法都可以避免内存碎片</li></ul></li><li><p>可预测的停顿时间模型（软实时 soft real-time）：</p><ul><li>可以指定在 M 毫秒的时间片段内，消耗在 GC 上的时间不得超过 N 毫秒</li><li>由于分块的原因，G1 可以只选取部分区域进行内存回收，这样缩小了回收的范围，对于全局停顿情况也能得到较好的控制</li><li>G1 跟踪各个 Region 里面的垃圾堆积的价值大小（回收所获得的空间大小以及回收所需时间，通过过去回收的经验获得），在后台维护一个<strong>优先列表</strong>，每次根据允许的收集时间优先回收价值最大的 Region，保证了 G1 收集器在有限的时间内可以获取尽可能高的收集效率</li></ul><ul><li>相比于 CMS GC，G1 未必能做到 CMS 在最好情况下的延时停顿，但是最差情况要好很多</li><li>通过<code>-XX：MaxGCPauseMillis</code>参数指定的停顿时间只意味着垃圾收集发生之前的期望值</li></ul></li></ul><h4 id="G1-缺点"><a href="#G1-缺点" class="headerlink" title="G1 缺点"></a>G1 缺点</h4><ul><li>相较于 CMS，G1 还不具备全方位、压倒性优势。比如在用户程序运行过程中，G1 无论是为了垃圾收集产生的内存占用还是程序运行时的额外执行负载都要比 CMS 要高</li><li>从经验上来说，在小内存应用上 CMS 的表现大概率会优于 G1，而 G1 在大内存应用上则发挥其优势，平衡点在 6-8GB 之间</li></ul><h4 id="记忆集-RSet"><a href="#记忆集-RSet" class="headerlink" title="记忆集-RSet"></a>记忆集-RSet</h4><p>为解决对象跨代引用所带来的问题，垃圾收集器在新生代中建立了名为记忆集（Remembered Set）的数据结构，用以避免把整个老年代加进 GC Roots 扫描范围。</p><p>记忆集 Remembered Set 在新生代中，每个 Region 都有一个 Remembered Set，用来记录被哪些其他 Region 里的对象引用（谁引用了我就记录谁）</p><img src="https://img.jwt1399.top/img/202212211532056.png" style="zoom:67%;" /><p>通过写屏障来更新记忆集：</p><p>程序对 Reference 类型数据写操作时，产生一个写屏障 Write Barrier 暂时中断操作，检查该对象和 Reference 类型数据是否在不同的 Region（跨代引用），不同就将相关引用信息记录到 Reference 类型所属的 Region 的 Remembered Set 之中，进行内存回收时，在 GC 根节点的枚举范围中加入 Remembered Set 即可保证不对全堆扫描也不会有遗漏</p><h4 id="卡表-Card-Table"><a href="#卡表-Card-Table" class="headerlink" title="卡表-Card Table"></a>卡表-Card Table</h4><p>垃圾收集器在新生代中建立了<strong>记忆集</strong>这样的数据结构，可以理解为它是一个抽象类，具体实现记忆集的三种方式：</p><ul><li><p>字长精度：每个记录精确到一个机器字长（就是处理器的寻址位数，如常见的32位或64位，这个精度决定了机器访问物理内存地址的指针长度），该字包含跨代指针。</p></li><li><p>对象精度：每个记录精确到一个对象，该对象里有字段含有跨代指针。</p></li><li><p>**卡精度(卡表)**：每个记录精确到一块内存区域，该区域内有对象含有跨代指针。</p></li></ul><blockquote><p>卡表（Card Table）在老年代中，是一种对记忆集的具体实现，主要定义了记忆集的记录精度、与堆内存的映射关系等</p></blockquote><p>卡表中的每一个元素都对应着一块特定大小的内存块，这个内存块称之为卡页（card page），当存在跨代引用时，会将卡页标记为 dirty，在垃圾收集发生时，只要筛选出卡表中 dirty 的元素，就能轻易得出哪些卡页内存块中包含跨代指针，把它们加入GC Roots中一并扫描。JVM 对于卡页的维护也是通过写屏障的方式</p><p><img src="https://img.jwt1399.top/img/202212211651174.png"></p><h4 id="写屏障-Write-Barrier"><a href="#写屏障-Write-Barrier" class="headerlink" title="写屏障-Write Barrier"></a>写屏障-Write Barrier</h4><blockquote><p>我们已经解决了如何使用记忆集来缩减GC Roots扫描范围的问题，但还没有解决卡表元素如何维护的问题，例如它们何时变脏、谁来把它们变脏等。</p></blockquote><p><strong>卡表元素何时变脏</strong></p><p>有其他分代区域中对象引用了本区域对象时，其对应的卡表元素就应该变脏，变脏时间点原则上应该发生在引用类型字段赋值的那一刻。</p><p><strong>如何变脏，即如何在对象赋值的那一刻去更新维护卡表呢？</strong></p><p>在HotSpot虚拟机里是通过写屏障（Write Barrier）技术维护卡表状态的。</p><p>写屏障可以看作在虚拟机层面对“引用类型字段赋值”这个动作的AOP切面，在引用对象赋值时会产生一个环形（Around）通知，供程序执行额外的动作，也就是说赋值的前后都在写屏障的覆盖范畴内。在赋值前的部分的写屏障叫作写前屏障（Pre-Write Barrier），在赋值后的则叫作写后屏障（Post-Write Barrier）。HotSpot虚拟机的许多收集器中都有使用到写屏障，但直至G1收集器出现之前，其他收集器都只用到了写后屏障。</p><p>应用写屏障后，虚拟机就会为所有赋值操作生成相应的指令，一旦收集器在写屏障中增加了更新卡表操作，无论更新的是不是老年代对新生代对象的引用，每次只要对引用进行更新，就会产生额外的开销，不过这个开销与Minor GC时扫描整个老年代的代价相比还是低得多的。</p><h4 id="回收集-CSet"><a href="#回收集-CSet" class="headerlink" title="回收集-CSet"></a>回收集-CSet</h4><p>Collection Set 代表每次 GC 暂停时回收的一系列目标分区，在任意一次收集暂停中，CSet 所有分区都会被释放，内部存活的对象都会被转移到分配的空闲分区中。年轻代收集 CSet 只容纳年轻代分区，而混合收集会通过启发式算法，在老年代候选回收分区中，筛选出回收收益最高的分区添加到 CSet 中</p><p>CSet根据两种不同的回收类型分为两种不同CSet。</p><ul><li><p>CSet of Young Collection</p><ul><li>只专注回收 Young Region 跟 Survivor Region</li></ul></li><li><p>CSet of Mix Collection</p><ul><li><p>则会通过RSet计算Region中对象的活跃度，</p></li><li><p>活跃度阈值<code>-XX:G1MixedGCLiveThresholdPercent</code>(默认85%)，只有活跃度高于这个阈值的才会准入CSet</p></li><li><p>还可通过<code>-XX:G1OldCSetRegionThresholdPercent</code>(默认10%)设置，CSet跟整个堆的比例的数量上限。</p></li></ul></li></ul><h4 id="工作原理"><a href="#工作原理" class="headerlink" title="工作原理"></a>工作原理</h4><p>G1 中提供了三种垃圾回收模式：YoungGC、Mixed GC 和 Full GC，在不同的条件下被触发</p><ul><li>当堆内存使用达到一定值（默认 45%）时，开始老年代并发标记过程</li><li>标记完成马上开始混合回收过程</li></ul><img src="https://img.jwt1399.top/img/202212211518167.png" style="zoom: 50%;" /><p>顺时针：Minor GC → Minor GC + Concurrent Mark → Mixed GC 顺序，进行垃圾回收</p><ul><li><p><strong>Minor GC</strong>：发生在年轻代的 GC 算法，一般对象（除了巨型对象）都是在 Eden 区 中分配内存，当Eden 区被耗尽无法申请内存时，就会触发一次 Minor GC，G1 停止应用程序的执行 STW，把活跃对象放入老年代，垃圾对象回收</p><p><strong>回收过程</strong>：</p><ol><li>扫描根：根引用连同 RSet 记录的外部引用作为扫描存活对象的入口</li><li>更新 RSet：处理 dirty card queue 更新 RSet，此后 RSet 准确的反映对象的引用关系<ul><li>dirty card queue：类似缓存，产生了引用先记录在这里，然后更新到 RSet</li><li>作用：产生引用直接更新 RSet 需要线程同步开销很大，使用队列性能好</li></ul></li><li>处理 RSet：识别被老年代对象指向的 Eden 中的对象，这些被指向的对象被认为是存活的对象，把需要回收的分区放入 Young CSet 中进行回收</li><li>复制对象：Eden 区内存段中存活的对象会被复制到 survivor 区，survivor 区内存段中存活的对象如果年龄未达阈值，年龄会加1，达到阀值会被会被复制到 Old 区中空的内存分段，如果 survivor 空间不够，Eden 空间的部分数据会直接晋升到老年代空间</li><li>处理引用：处理 Soft，Weak，Phantom，JNI Weak  等引用，最终 Eden 空间的数据为空，GC 停止工作</li></ol></li><li><p>**Concurrent Mark **：</p><ul><li><p><strong>初始标记</strong>（Initial Marking）：仅仅只是标记一下GC Roots能直接关联到的对象，这个阶段是 STW 的，并且会触发一次 Minor GC</p></li><li><p><strong>并发标记</strong> (Concurrent Marking）：从GC Root开始对堆中对象进行可达性分析，递归扫描整个堆里的对象图，找出要回收的对象，这阶段耗时较长，但可与用户程序并发执行。当对象图扫描完成以后，还要重新处理SATB记录下的在并发时有引用变动的对象。</p><ul><li>老年代占用堆空间比例达到阈值时，就会进行并发标记 </li><li><code>-XX:InitiatingHeapOccupancyPercent</code> 设置阈值（默认45%）</li></ul></li><li><p><strong>最终标记</strong>（Final Marking）：为了修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录，虚拟机将这段时间对象变化记录在线程的 Remembered Set Logs 里面，最终标记阶段需要把 Remembered Set Logs 的数据合并到 Remembered Set 中，对用户线程做另一个短暂 STW，用于处理并发阶段结束后仍遗留下来的最后那少量的SATB记录。但是可并行执行（<strong>防止漏标</strong>）</p></li><li><p>筛选回收（Live Data Counting and Evacuation）：首先对 CSet 中各个 Region 中的回收价值和成本进行排序，根据用户所期望的 GC 停顿时间来制定回收计划，也需要 STW，由多条收集器线程并行完成的。</p></li></ul><p><img src="https://img.jwt1399.top/img/202212201631402.jpg"></p></li><li><p><strong>Mixed GC</strong>：当很多对象晋升到老年代时，为了避免堆内存被耗尽，虚拟机会触发一个混合的垃圾收集器，即 Mixed GC，除了回收整个 young region，还会回收一部分的 old region。</p><p>注意：<strong>是一部分老年代，而不是全部老年代</strong>，选择回收价值高的老年代 region 进行回收，从而对垃圾回收的时间进行控制</p><p><code>-XX:MaxGCPauseMillis</code>：设置期望达到的最大 GC 停顿时间指标，JVM会尽力实现，但不保证达到，默认值是 200ms</p></li><li><p><strong>Full GC</strong>：对象内存分配速度过快，Mixed GC 来不及回收，导致老年代被填满，就会触发一次 Full GC，G1 的 Full GC 算法就是单线程执行的垃圾回收，会导致长时间的暂停时间，需要进行不断的调优，尽可能的避免 Full GC</p><p>产生 Full GC 的原因：</p><ul><li>晋升时没有足够的空间存放晋升的对象</li><li>并发处理过程完成之前空间耗尽，浮动垃圾</li></ul></li></ul><h4 id="相关参数"><a href="#相关参数" class="headerlink" title="相关参数"></a>相关参数</h4><ul><li><code>-XX:+UseG1GC</code>：手动指定使用 G1 垃圾收集器执行内存回收任务</li><li><code>-XX:G1HeapRegionSize</code>：设置每个 Region 的大小。值是 2 的幂，范围是 1MB 到 32MB 之间，目标是根据最小的 Java 堆大小划分出约 2048 个区域，默认是堆内存的 1&#x2F;2000</li><li><code>-XX:MaxGCPauseMillis</code>：设置期望达到的最大 GC 停顿时间指标，JVM会尽力实现，但不保证达到，默认值是 200ms</li><li><code>-XX:+ParallelGcThread</code>：设置 STW 时 GC 线程数的值，最多设置为 8</li><li><code>-XX:ConcGCThreads</code>：设置并发标记线程数，设置为并行垃圾回收线程数 ParallelGcThreads 的1&#x2F;4左右</li><li><code>-XX:InitiatingHeapOccupancyPercent</code>：设置并发标记阈值，默认值是 45</li><li><code>-XX:+ClassUnloadingWithConcurrentMark</code>：并发标记类卸载，默认启用，所有对象都经过并发标记后，就可以知道哪些类不再被使用，当一个类加载器的所有类都不再使用，则卸载它所加载的所有类</li><li><code>-XX:G1NewSizePercent</code>：新生代占用整个堆内存的最小百分比（默认5％） </li><li><code>-XX:G1MaxNewSizePercent</code>：新生代占用整个堆内存的最大百分比（默认60％） </li><li><code>-XX:G1ReservePercent=10</code>：保留内存区域，防止Survivor中的 to 区溢出</li></ul><h4 id="调优"><a href="#调优" class="headerlink" title="调优"></a>调优</h4><p>G1 的设计原则就是简化 JVM 性能调优，只需要简单的三步即可完成调优：</p><ol><li>开启 G1 垃圾收集器</li><li>设置堆的最大内存</li><li>设置最大的停顿时间（STW）</li></ol><p>不断调优暂停时间指标：</p><ul><li><code>-XX:MaxGCPauseMillis=x</code> 可以设置启动应用程序暂停的时间，G1会根据这个参数选择 CSet 来满足响应时间的设置</li><li>设置到 100ms 或者 200ms 都可以（不同情况下会不一样），但设置成50ms就不太合理</li><li>暂停时间设置的太短，就会导致出现 G1 跟不上垃圾产生的速度，最终退化成 Full GC</li><li>对这个参数的调优是一个持续的过程，逐步调整到最佳状态</li></ul><p>不要设置新生代和老年代的大小：</p><ul><li>避免使用 <code>-Xmn</code> 或 <code>-XX:NewRatio</code> 等相关选项显式设置年轻代大小，G1 收集器在运行的时候会调整新生代和老年代的大小，从而达到我们为收集器设置的暂停时间目标</li><li>设置了新生代大小相当于放弃了 G1 的自动调优，我们只需要设置整个堆内存的大小，剩下的交给 G1 自己去分配各个代的大小</li></ul><h3 id="6-ZGC"><a href="#6-ZGC" class="headerlink" title="6.ZGC"></a>6.ZGC</h3><blockquote><p>ZGC 收集器是JDK 11中推出的一个可伸缩的、低延迟的垃圾收集器，基于 Region 内存布局的，不设分代，使用了<strong>读屏障</strong>、<strong>染色指针</strong>和<strong>内存多重映射</strong>等技术来实现<strong>可并发的标记-整理算法</strong></p></blockquote><ul><li>在 CMS 和 G1 中都用到了写屏障，而 ZGC 用到了读屏障</li><li>染色指针：直接<strong>将少量额外的信息存储在指针上的技术</strong>，从 64 位的指针中拿高 4 位来标识对象此时的状态<ul><li>染色指针可以使某个 Region 的存活对象被移走之后，这个 Region 立即就能够被释放和重用</li><li>可以直接从指针中看到引用对象的三色标记状态（Marked0、Marked1）、是否进入了重分配集、是否被移动过（Remapped）、是否只能通过 finalize() 方法才能被访问到（Finalizable）</li><li>可以大幅减少在垃圾收集过程中内存屏障的使用数量，写屏障的目的通常是为了记录对象引用的变动情况，如果将这些信息直接维护在指针中，显然就可以省去一些专门的记录操作</li><li>可以作为一种可扩展的存储结构用来记录更多与对象标记、重定位过程相关的数据</li></ul></li><li>内存多重映射：多个虚拟地址指向同一个物理地址</li></ul><ul><li>可并发的标记整理算法：染色指针标识对象是否被标记或移动，读屏障保证在每次应用程序或 GC 程序访问对象时先根据染色指针的标识判断是否被移动，如果被移动就根据转发表访问新的移动对象，<strong>并更新引用</strong>，不会像 G1 一样必须等待垃圾回收完成才能访问</li></ul><p>ZGC 目标：</p><ul><li>停顿时间不会超过 10ms</li><li>停顿时间不会随着堆的增大而增大（不管多大的堆都能保持在 10ms 以下）</li><li>可支持几百 M，甚至几 T 的堆大小（最大支持4T）</li></ul><p>ZGC 的工作过程可以分为 4 个阶段：</p><ul><li><strong>并发标记</strong>（Concurrent Mark）： 遍历对象图做可达性分析的阶段，做可达性分析的阶段，前后也要经过类似于G1的初始标记和最终标记的短暂停顿。与G1不同的是，ZGC的标记是在指针上而不是在对象上进行的，标记阶段会更新染色指针中的Marked 0、Marked 1标志位。</li><li><strong>并发预备重分配</strong>（Concurrent Prepare for Relocate）：根据特定的查询条件统计得出本次收集过程要清理哪些 Region，将这些 Region 组成重分配集（Relocation Set）</li><li><strong>并发重分配</strong>（Concurrent Relocate）： 重分配是 ZGC 执行过程中的核心阶段，这个过程要把重分配集中的存活对象复制到新的 Region 上，并为重分配集中的<strong>每个 Region 维护一个转发表</strong>（Forward Table），记录从旧地址到新地址的转向关系</li><li><strong>并发重映射</strong>（Concurrent Remap）：修正整个堆中指向重分配集中旧对象的所有引用，ZGC 的并发映射并不是一个必须要立即完成的任务，ZGC 很巧妙地把并发重映射阶段要做的工作，合并到下一次垃圾收集循环中的并发标记阶段里去完成，因为都是要遍历所有对象，这样合并节省了一次遍历的开销</li></ul><p>ZGC 几乎在所有地方并发执行的，除了初始标记的是 STW 的，但这部分的实际时间是非常少的，所以响应速度快，在尽可能对吞吐量影响不大的前提下，实现在任意堆内存大小下都可以把垃圾收集的停顿时间限制在十毫秒以内的低延迟</p><p>优点：高吞吐量、低延迟</p><p>缺点：浮动垃圾，当 ZGC 准备要对一个很大的堆做一次完整的并发收集，其全过程要持续十分钟以上，由于应用的对象分配速率很高，将创造大量的新对象产生浮动垃圾</p><p>对比：</p><p>G1 需要通过写屏障来维护记忆集，才能处理跨代指针，得以实现Region的增量回收。记忆集要占用大量的内存空间，写屏障也对正常程序运行造成额外负担，这些都是权衡选择的代价。</p><p>ZGC就完全没有使用记忆集，它甚至连分代都没有，连像CMS中那样只记录新生代和老年代间引用的卡表也不需要，因而完全没有用到写屏障，所以给用户线程带来的运行负担也要小得多。可是，当 ZGC 准备要对一个很大的堆做一次完整的并发收集，其全过程要持续十分钟以上，由于应用的对象分配速率很高，将创造大量的新对象产生浮动垃圾</p><p><a href="https://tech.meituan.com/2020/08/06/new-zgc-practice-in-meituan.html">新一代垃圾回收器ZGC的探索与实践 - 美团技术团队 (meituan.com)</a></p><h3 id="对比总结"><a href="#对比总结" class="headerlink" title="对比总结"></a>对比总结</h3><ul><li>最小化地使用内存和并行开销，选 Serial GC</li><li>最大化应用程序的吞吐量，选 Parallel GC</li><li>最小化 GC 的中断或停顿时间，选 CMS GC</li></ul><p><img src="https://img.jwt1399.top/img/202212181620954.png"></p><p>jdk8环境下，默认使用 Parallel Scavenge + Parallel Old</p><h1 id="④类加载"><a href="#④类加载" class="headerlink" title="④类加载"></a>④类加载</h1><h2 id="❶类文件结构"><a href="#❶类文件结构" class="headerlink" title="❶类文件结构"></a>❶类文件结构</h2><pre class="line-numbers language-java"><code class="language-java">ClassFile <span class="token punctuation">{</span>    u4             magic<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//Class 文件的标志</span>    u2             minor_version<span class="token punctuation">;</span><span class="token comment" spellcheck="true">//Class 的小版本号</span>    u2             major_version<span class="token punctuation">;</span><span class="token comment" spellcheck="true">//Class 的大版本号</span>    u2             constant_pool_count<span class="token punctuation">;</span><span class="token comment" spellcheck="true">//常量池的数量</span>    cp_info        constant_pool<span class="token punctuation">[</span>constant_pool_count<span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//常量池</span>    u2             access_flags<span class="token punctuation">;</span><span class="token comment" spellcheck="true">//Class 的访问标记</span>    u2             this_class<span class="token punctuation">;</span><span class="token comment" spellcheck="true">//当前类</span>    u2             super_class<span class="token punctuation">;</span><span class="token comment" spellcheck="true">//父类</span>    u2             interfaces_count<span class="token punctuation">;</span><span class="token comment" spellcheck="true">//接口</span>    u2             interfaces<span class="token punctuation">[</span>interfaces_count<span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//一个类可以实现多个接口</span>    u2             fields_count<span class="token punctuation">;</span><span class="token comment" spellcheck="true">//Class 文件的字段属性</span>    field_info     fields<span class="token punctuation">[</span>fields_count<span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//一个类可以有多个字段</span>    u2             methods_count<span class="token punctuation">;</span><span class="token comment" spellcheck="true">//Class 文件的方法数量</span>    method_info    methods<span class="token punctuation">[</span>methods_count<span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//一个类可以有个多个方法</span>    u2             attributes_count<span class="token punctuation">;</span><span class="token comment" spellcheck="true">//此类的属性表中的属性数</span>    attribute_info attributes<span class="token punctuation">[</span>attributes_count<span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//属性表集合</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><table><thead><tr><th>类型</th><th>名称</th><th>说明</th><th>长度</th><th>数量</th></tr></thead><tbody><tr><td>u4</td><td>magic</td><td>魔数，识别类文件格式</td><td>4个字节</td><td>1</td></tr><tr><td>u2</td><td>minor_version</td><td>副版本号(小版本)</td><td>2个字节</td><td>1</td></tr><tr><td>u2</td><td>major_version</td><td>主版本号(大版本)</td><td>2个字节</td><td>1</td></tr><tr><td>u2</td><td>constant_pool_count</td><td>常量池计数器</td><td>2个字节</td><td>1</td></tr><tr><td>cp_info</td><td>constant_pool</td><td>常量池表</td><td>n个字节</td><td>constant_pool_count-1</td></tr><tr><td>u2</td><td>access_flags</td><td>访问标识</td><td>2个字节</td><td>1</td></tr><tr><td>u2</td><td>this_class</td><td>类索引</td><td>2个字节</td><td>1</td></tr><tr><td>u2</td><td>super_class</td><td>父类索引</td><td>2个字节</td><td>1</td></tr><tr><td>u2</td><td>interfaces_count</td><td>接口计数</td><td>2个字节</td><td>1</td></tr><tr><td>u2</td><td>interfaces</td><td>接口索引集合</td><td>2个字节</td><td>interfaces_count</td></tr><tr><td>u2</td><td>fields_count</td><td>字段计数器</td><td>2个字节</td><td>1</td></tr><tr><td>field_info</td><td>fields</td><td>字段表</td><td>n个字节</td><td>fields_count</td></tr><tr><td>u2</td><td>methods_count</td><td>方法计数器</td><td>2个字节</td><td>1</td></tr><tr><td>method_info</td><td>methods</td><td>方法表</td><td>n个字节</td><td>methods_count</td></tr><tr><td>u2</td><td>attributes_count</td><td>属性计数器</td><td>2个字节</td><td>1</td></tr><tr><td>attribute_info</td><td>attributes</td><td>属性表</td><td>n个字节</td><td>attributes_count</td></tr></tbody></table><p>Class 文件格式只有两种数据类型：无符号数和表</p><ul><li>无符号数属于基本的数据类型，以 u1、u2、u4、u8 来分别代表1个字节、2个字节、4个字节和8个字节的无符号数，无符号数可以用来描述数字、索引引用、数量值或者按照 UTF-8 编码构成字符串</li><li>表是由多个无符号数或者其他表作为数据项构成的复合数据类型，表都以 <code>_info</code> 结尾，用于描述有层次关系的数据，整个 Class 文件本质上就是一张表，由于表没有固定长度，所以通常会在其前面加上个数说明</li></ul><p>Class 文件获取方式：</p><ul><li>HelloWorld.java 执行 <code>javac -g xxx.java</code>指令</li><li>写入文件指令 <code>javap -v xxx.class &gt;xxx.txt</code></li><li>IDEA 插件 jclasslib 、桌面版 <a href="https://github.com/ingokegel/jclasslib">jclasslib</a>、<a href="https://github.com/Konloch/bytecode-viewer">bytecode-viewer</a></li></ul><img src="https://img.jwt1399.top/img/202212221701410.jpeg" style="zoom: 25%;" /><h3 id="案例"><a href="#案例" class="headerlink" title="案例"></a>案例</h3><p>接下来以下面代码进行讲解类文件结构</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">package</span> JJTest<span class="token punctuation">;</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Demo</span> <span class="token punctuation">{</span>    <span class="token keyword">private</span> <span class="token keyword">int</span> num <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>    <span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">add</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>        num <span class="token operator">=</span> num <span class="token operator">+</span> <span class="token number">2</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> num<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>上面代码的字节码对应的16进制</p><p><img src="https://img.jwt1399.top/img/202212231012766.png"></p><h3 id="魔数"><a href="#魔数" class="headerlink" title="魔数"></a>魔数</h3><p>每个 Class 文件开头的 4 个字节的无符号整数称为魔数（Magic Number），是 Class 文件的标识符，代表这是一个能被虚拟机接受的有效合法的 Class 文件，</p><ul><li><p>魔数值固定为 <code>0xCAFEBABE</code>，不符合则会抛出错误</p></li><li><p>使用魔数而不是扩展名来进行识别主要是基于安全方面的考虑，因为文件扩展名可以随意地改动</p></li></ul><h3 id="版本"><a href="#版本" class="headerlink" title="版本"></a>版本</h3><p>4 个 字节，5 6两个字节代表的是编译的副版本号 minor_version，7 8 两个字节是编译的主版本号 major_version</p><ul><li>不同版本的 Java 编译器编译的 Class 文件对应的版本是不一样的，高版本的 Java 虚拟机可以执行由低版本编译器生成的 Class 文件，反之 JVM 会抛出异常 <code>java.lang.UnsupportedClassVersionError</code></li><li>案例中<code>00 34</code>： 转换成 10 进制 16*3+4 &#x3D; 52，代表 JDK1.8</li></ul><table><thead><tr><th>主版本（十进制）</th><th>副版本（十进制）</th><th>编译器版本</th></tr></thead><tbody><tr><td>45</td><td>3</td><td>1.1</td></tr><tr><td>46</td><td>0</td><td>1.2</td></tr><tr><td>47</td><td>0</td><td>1.3</td></tr><tr><td>48</td><td>0</td><td>1.4</td></tr><tr><td>49</td><td>0</td><td>1.5</td></tr><tr><td>50</td><td>0</td><td>1.6</td></tr><tr><td>51</td><td>0</td><td>1.7</td></tr><tr><td>52</td><td>0</td><td>1.8</td></tr><tr><td>53</td><td>0</td><td>1.9</td></tr><tr><td>54</td><td>0</td><td>1.10</td></tr><tr><td>55</td><td>0</td><td>1.11</td></tr></tbody></table><h3 id="常量池计数器"><a href="#常量池计数器" class="headerlink" title="常量池计数器"></a>常量池计数器</h3><ul><li><p>由于常量池中常量的数量不固定，所以放置 u2 类型的无符号数表示常量池计数器（constant_pool_count）</p></li><li><p>常量池计数器值：从1开始，表示常量池中有多少项常量。即constant_pool_count&#x3D;1表示常量池中有0个常量项。</p></li><li><p>这个计数器是从 1 而不是 0 开始，是为了满足后面某些指向常量池的索引值的数据在特定情况下需要表达不引用任何一个常量池项目，这种情况可用索引值 0 来表示</p></li><li><p>案例中 <code>00 16</code>：转换成10进制 22，因此有 21 个常量池</p></li></ul><h3 id="常量池"><a href="#常量池" class="headerlink" title="常量池"></a>常量池</h3><p>constant_pool 是一种表结构，以 1 ~ constant_pool_count - 1 为索引，表明有多少个常量池表项。表项中存放编译时期生成的各种字面量和符号引用，这部分内容将在类加载后进入方法区的运行时常量池</p><ul><li><p>字面量（Literal） ：基本数据类型、字符串类型常量、声明为 final 的常量值等</p></li><li><p>符号引用（Symbolic References）：类和接口的全限定名、字段的名称和描述符、方法的名称和描述符</p><ul><li><p>全限定名：com&#x2F;test&#x2F;Demo 这个就是类的全限定名，仅仅是把包名的 <code>.</code> 替换成 <code>/</code>，为了使连续的多个全限定名之间不产生混淆，在使用时最后一般会加入一个 <code>;</code> 表示全限定名结束</p></li><li><p>简单名称：指没有类型和参数修饰的方法或者字段名称，比如字段 x 的简单名称就是 x</p></li><li><p>描述符：用来描述字段的数据类型、方法的参数列表（包括数量、类型以及顺序）和返回值</p><table><thead><tr><th>标志符</th><th>含义</th></tr></thead><tbody><tr><td>B</td><td>基本数据类型 byte</td></tr><tr><td>C</td><td>基本数据类型 char</td></tr><tr><td>D</td><td>基本数据类型 double</td></tr><tr><td>F</td><td>基本数据类型 float</td></tr><tr><td>I</td><td>基本数据类型 int</td></tr><tr><td>J</td><td>基本数据类型 long</td></tr><tr><td>S</td><td>基本数据类型 short</td></tr><tr><td>Z</td><td>基本数据类型 boolean</td></tr><tr><td>V</td><td>代表 void 类型</td></tr><tr><td>L</td><td>对象类型，比如：<code>Ljava/lang/Object;</code>，不同方法间用<code>;</code>隔开</td></tr><tr><td>[</td><td>数组类型，代表一维数组。比如：<code>double[][][] is [[[D</code></td></tr></tbody></table></li></ul></li></ul><p>常量池中常量类型</p><p>常量池的每一项常量都是一个表，表开始的第一位是一个 u1 类型的标志位（tag），代表当前这个常量属于哪种常量类型。</p><p><img src="https://img.jwt1399.top/img/202212230943482.png"></p><p>18 种常量没有出现 byte、short、char，boolean 的原因：编译之后都可以理解为 Integer</p><ul><li>案例中 <code>0a 00 04 00 12</code>： <ul><li>0a –&gt; 10 –&gt; CONSTANT_Methodref_info  –&gt; u2 u2</li><li>u2：00 04 –&gt; CONSTANT_Class_info #4 –&gt; CONSTANT_Class_info #21 –&gt; java&#x2F;lang&#x2F;Object</li><li>u2：00 12 –&gt; CONSTANT_NameAndType_info #18 –&gt; CONSTANT_Class_info #7  &amp; CONSTANT_Class_info #8  –&gt; &lt;init&gt; &amp; ()V 【无参返回值void】</li></ul></li></ul><h3 id="访问标识"><a href="#访问标识" class="headerlink" title="访问标识"></a>访问标识</h3><p>访问标识（access_flag），又叫访问标志、访问标记，该标识用两个字节表示，用于识别一些类或者接口层次的访问信息，包括这个 Class 是类还是接口，是否定义为 public类型，是否定义为 abstract类型等</p><ul><li>类的访问权限通常为 ACC_ 开头的常量</li><li>每一种类型的表示都是通过设置访问标记的 32 位中的特定位来实现的，比如若是 public final 的类，则该标记为 <code>ACC_PUBLIC | ACC_FINAL</code></li><li>使用 <code>ACC_SUPER</code> 可以让类更准确地定位到父类的方法，确定类或接口里面的 invokespecial 指令使用的是哪一种执行语义，现代编译器都会设置并且使用这个标记</li><li>案例中 <code>00 21</code> ： 0x0001 + 0x0020 代表 ACC_PUBLIC 和 ACC_SUPER</li></ul><table><thead><tr><th>标志名称</th><th>标志值</th><th>含义</th></tr></thead><tbody><tr><td>ACC_PUBLIC</td><td>0x0001</td><td>标志为 public 类型</td></tr><tr><td>ACC_FINAL</td><td>0x0010</td><td>标志被声明为 final，只有类可以设置</td></tr><tr><td>ACC_SUPER</td><td>0x0020</td><td>标志允许使用 invokespecial 字节码指令的新语义，JDK1.0.2之后编译出来的类的这个标志默认为真，使用增强的方法调用父类方法</td></tr><tr><td>ACC_INTERFACE</td><td>0x0200</td><td>标志这是一个接口</td></tr><tr><td>ACC_ABSTRACT</td><td>0x0400</td><td>是否为 abstract 类型，对于接口或者抽象类来说，次标志值为真，其他类型为假</td></tr><tr><td>ACC_SYNTHETIC</td><td>0x1000</td><td>标志此类并非由用户代码产生（由编译器产生的类，没有源码对应）</td></tr><tr><td>ACC_ANNOTATION</td><td>0x2000</td><td>标志这是一个注解</td></tr><tr><td>ACC_ENUM</td><td>0x4000</td><td>标志这是一个枚举</td></tr></tbody></table><h3 id="索引集合"><a href="#索引集合" class="headerlink" title="索引集合"></a>索引集合</h3><p>本类索引、父类索引、接口索引集合</p><ul><li><p>本类索引用于确定这个类的全限定名</p><ul><li>案例中<code>00 03</code> ： cp_info #3 【即3号常量池】–&gt; cp_info #20 –&gt; JJTest&#x2F;Demo</li></ul></li><li><p>父类索引用于确定这个类的父类的全限定名，Java 语言不允许多重继承，所以父类索引只有一个，除了Object 之外，所有的 Java 类都有父类，因此除了 java.lang.Object 外，所有 Java 类的父类索引都不为0</p><ul><li>案例中<code>00 04</code>：cp_info #4 –&gt; cp_info #21 –&gt; java&#x2F;lang&#x2F;Object</li></ul></li><li><p>接口索引集合就用来描述这个类实现了哪些接口</p><ul><li>interfaces_count 项的值表示当前类或接口的直接超接口数量</li><li>interfaces[] 接口索引集合，被实现的接口将按 implements 语句后的接口顺序从左到右排列在接口索引集合中</li><li>案例中<code>00 00</code>：表示当前类没有实现接口</li></ul></li></ul><table><thead><tr><th>长度</th><th>含义</th></tr></thead><tbody><tr><td>u2</td><td>this_class</td></tr><tr><td>u2</td><td>super_class</td></tr><tr><td>u2</td><td>interfaces_count</td></tr><tr><td>u2</td><td>interfaces[interfaces_count]</td></tr></tbody></table><h3 id="字段表"><a href="#字段表" class="headerlink" title="字段表"></a>字段表</h3><p>字段 fields 用于描述接口或类中声明的变量，包括类变量以及实例变量，但不包括方法内部、代码块内部声明的局部变量（local variables）以及从父类或父接口继承。字段叫什么名字、被定义为什么数据类型，都是无法固定的，只能引用常量池中的常量来描述</p><p>fields_count（字段计数器），表示当前 class 文件 fields 表的成员个数，用两个字节来表示</p><ul><li>案例中 <code>00 01</code>：表示当前 class 文件只有一个字段，即num</li></ul><p>fields[]（字段表）：</p><ul><li>fields表中的每个成员都是一个 fields_info 结构的数据项，用于表示当前类或接口中某个字段的完整描述</li></ul><table><thead><tr><th>标志名称</th><th>标志值</th><th>含义</th><th>数量</th></tr></thead><tbody><tr><td>u2</td><td>access_flags</td><td>访问标志</td><td>1</td></tr><tr><td>u2</td><td>name_index</td><td>字段名索引</td><td>1</td></tr><tr><td>u2</td><td>descriptor_index</td><td>描述符索引</td><td>1</td></tr><tr><td>u2</td><td>attributes_count</td><td>属性计数器</td><td>1</td></tr><tr><td>attribute_info</td><td>attributes</td><td>属性集合</td><td>attributes_count</td></tr></tbody></table><p>字段访问标志：</p><ul><li>案例中 <code>00 02</code>：ACC_PRIVATE，字段为private</li></ul><table><thead><tr><th>标志名称</th><th>标志值</th><th>含义</th></tr></thead><tbody><tr><td>ACC_PUBLIC</td><td>0x0001</td><td>字段是否为public</td></tr><tr><td>ACC_PRIVATE</td><td>0x0002</td><td>字段是否为private</td></tr><tr><td>ACC_PROTECTED</td><td>0x0004</td><td>字段是否为protected</td></tr><tr><td>ACC_STATIC</td><td>0x0008</td><td>字段是否为static</td></tr><tr><td>ACC_FINAL</td><td>0x0010</td><td>字段是否为final</td></tr><tr><td>ACC_VOLATILE</td><td>0x0040</td><td>字段是否为volatile</td></tr><tr><td>ACC_TRANSTENT</td><td>0x0080</td><td>字段是否为transient</td></tr><tr><td>ACC_SYNCHETIC</td><td>0x1000</td><td>字段是否为由编译器自动产生</td></tr><tr><td>ACC_ENUM</td><td>0x4000</td><td>字段是否为enum</td></tr></tbody></table><p>字段名索引：根据该值查询常量池中的指定索引项即可</p><ul><li>案例中 <code>00 05</code>：cp_info #5 –&gt;num</li></ul><p>描述符索引：用来描述字段的数据类型、方法的参数列表和返回值</p><ul><li>案例中 <code>00 06</code>：cp_info #6  –&gt; I –&gt; 整型</li></ul><table><thead><tr><th>字符</th><th>类型</th><th>含义</th></tr></thead><tbody><tr><td>B</td><td>byte</td><td>有符号字节型树</td></tr><tr><td>C</td><td>char</td><td>Unicode字符，UTF-16编码</td></tr><tr><td>D</td><td>double</td><td>双精度浮点数</td></tr><tr><td>F</td><td>float</td><td>单精度浮点数</td></tr><tr><td>I</td><td>int</td><td>整型数</td></tr><tr><td>J</td><td>long</td><td>长整数</td></tr><tr><td>S</td><td>short</td><td>有符号短整数</td></tr><tr><td>Z</td><td>boolean</td><td>布尔值true&#x2F;false</td></tr><tr><td>V</td><td>void</td><td>代表void类型</td></tr><tr><td>L Classname</td><td>reference</td><td>一个名为Classname的实例</td></tr><tr><td>[</td><td>reference</td><td>一个一维数组</td></tr></tbody></table><p>属性表集合：属性个数存放在 attribute_count 中，属性具体内容存放在 attribute 数组中，一个字段还可能拥有一些属性，用于存储更多的额外信息，比如常量的初始化值、一些注释信息等，对于常量属性而言，attribute_length 值恒为2</p><ul><li>案例中 <code>00 00</code>：当前字段没有属性</li></ul><pre class="line-numbers language-java"><code class="language-java">ConstantValue_attribute<span class="token punctuation">{</span>    u2 attribute_name_index<span class="token punctuation">;</span>    u4 attribute_length<span class="token punctuation">;</span>    u2 constantvalue_index<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="方法表"><a href="#方法表" class="headerlink" title="方法表"></a>方法表</h3><p>方法表是 methods 指向常量池索引集合，其中每一个 method_info 项都对应着一个类或者接口中的方法信息，完整描述了每个方法的签名</p><ul><li>如果这个方法不是抽象的或者不是 native 的，字节码中就会体现出来</li><li>methods 表只描述当前类或接口中声明的方法，不包括从父类或父接口继承的方法<ul><li>methods 表可能会出现由编译器自动添加的方法，比如初始化方法 &lt;clinit&gt; 和实例化方法 &lt;init&gt;</li></ul></li></ul><p>要重载（Overload）一个方法，除了要与原方法具有相同的简单名称之外，还要求必须拥有一个与原方法不同的特征签名，特征签名就是一个方法中各个参数在常量池中的字段符号引用的集合，因为返回值不会包含在特征签名之中，因此 Java 语言里无法仅仅依靠返回值的不同来对一个已有方法进行重载。但在 Class 文件格式中，特征签名的范围更大一些，只要描述符不是完全一致的两个方法就可以共存</p><p>methods_count（方法计数器）：表示 class 文件 methods 表的成员个数，使用两个字节来表示</p><ul><li>案例中<code>00 02</code>：表示两个方法，&lt;init&gt; 和 add</li></ul><p>methods[]（方法表）：每个表项都是一个 method_info 结构，表示当前类或接口中某个方法的完整描述</p><p>方法表结构如下：</p><table><thead><tr><th>类型</th><th>名称</th><th>含义</th><th>数量</th></tr></thead><tbody><tr><td>u2</td><td>access_flags</td><td>访问标志</td><td>1</td></tr><tr><td>u2</td><td>name_index</td><td>字段名索引</td><td>1</td></tr><tr><td>u2</td><td>descriptor_index</td><td>描述符索引</td><td>1</td></tr><tr><td>u2</td><td>attrubutes_count</td><td>属性计数器</td><td>1</td></tr><tr><td>attribute_info</td><td>attributes</td><td>属性集合</td><td>attributes_count</td></tr></tbody></table><p>方法表访问标志：</p><table><thead><tr><th>标志名称</th><th>标志值</th><th>含义</th></tr></thead><tbody><tr><td>ACC_PUBLIC</td><td>0x0001</td><td>public，方法可以从包外访问</td></tr><tr><td>ACC_PRIVATE</td><td>0x0002</td><td>private，方法只能本类访问</td></tr><tr><td>ACC_PROTECTED</td><td>0x0004</td><td>protected，方法在自身和子类可以访问</td></tr><tr><td>ACC_STATIC</td><td>0x0008</td><td>static，静态方法</td></tr></tbody></table><ul><li><p>案例中<code>00 01</code>：ACC_PUBLIC，public，方法可以从包外访问</p></li><li><p>案例中<code>00 07</code>：&lt;init&gt; ，实例初始化方法</p></li><li><p>案例中<code>00 08</code>：()V，无参，返回值类型void</p></li><li><p>案例中<code>00 01</code>：当前方法有一个属性</p></li></ul><h3 id="属性表"><a href="#属性表" class="headerlink" title="属性表"></a>属性表</h3><p>属性表集合，指的是 Class 文件所携带的辅助信息，比如该 Class 文件的源文件的名称，以及任何带有 <code>RetentionPolicy.CLASS</code> 或者 <code>RetentionPolicy.RUNTIME</code> 的注解，这类信息通常被用于 Java 虚拟机的验证和运行，以及 Java 程序的调试。字段表、方法表都可以有自己的属性表，用于描述某些场景专有的信息</p><p>attributes_ count（属性计数器）：表示当前文件属性表的成员个数</p><ul><li>案例中<code>00 01</code>：表示有1个属性表</li></ul><p>attributes[]（属性表）：属性表的每个项的值必须是 attribute_info 结构</p><ul><li><p>案例中<code>00 10</code>：属性名索引 cp_info #16 –&gt;SourceFile</p></li><li><p>案例中<code>00 00 00 02</code>：属性长度为 2</p></li><li><p>案例中<code>00 11</code>：源码文件素引cp_info #17 –&gt;Demo.java</p></li><li><p>属性的通用格式：</p><pre class="line-numbers language-java"><code class="language-java">ConstantValue_attribute<span class="token punctuation">{</span>    u2 attribute_name_index<span class="token punctuation">;</span><span class="token comment" spellcheck="true">//属性名索引</span>    u4 attribute_length<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//属性长度</span>    u2 attribute_info<span class="token punctuation">;</span><span class="token comment" spellcheck="true">//属性表</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre></li></ul><p>属性类型：</p><table><thead><tr><th>属性名称</th><th>使用位置</th><th>含义</th></tr></thead><tbody><tr><td>Code</td><td>方法表</td><td>Java 代码编译成的字节码指令</td></tr><tr><td>ConstantValue</td><td>字段表</td><td>final 关键字定义的常量池</td></tr><tr><td>Deprecated</td><td>类、方法、字段表</td><td>被声明为 deprecated 的方法和字段</td></tr><tr><td>Exceptions</td><td>方法表</td><td>方法抛出的异常</td></tr><tr><td>EnclosingMethod</td><td>类文件</td><td>仅当一个类为局部类或者匿名类是才能拥有这个属性，这个属性用于标识这个类所在的外围方法</td></tr><tr><td>InnerClass</td><td>类文件</td><td>内部类列表</td></tr><tr><td>LineNumberTable</td><td>Code 属性</td><td>Java 源码的行号与字节码指令的对应关系</td></tr><tr><td>LocalVariableTable</td><td>Code 属性</td><td>方法的局部变量描述</td></tr><tr><td>StackMapTable</td><td>Code 属性</td><td>JDK1.6 中新增的属性，供新的类型检查检验器检查和处理目标方法的局部变量和操作数有所需要的类是否匹配</td></tr><tr><td>Signature</td><td>类，方法表，字段表</td><td>用于支持泛型情况下的方法签名</td></tr><tr><td>SourceFile</td><td>类文件</td><td>记录源文件名称</td></tr><tr><td>SourceDebugExtension</td><td>类文件</td><td>用于存储额外的调试信息</td></tr><tr><td>Syothetic</td><td>类，方法表，字段表</td><td>标志方法或字段为编泽器自动生成的</td></tr><tr><td>LocalVariableTypeTable</td><td>类</td><td>使用特征签名代替描述符，是为了引入泛型语法之后能描述泛型参数化类型而添加</td></tr><tr><td>RuntimeVisibleAnnotations</td><td>类，方法表，字段表</td><td>为动态注解提供支持</td></tr><tr><td>RuntimelnvisibleAnnotations</td><td>类，方法表，字段表</td><td>用于指明哪些注解是运行时不可见的</td></tr><tr><td>RuntimeVisibleParameterAnnotation</td><td>方法表</td><td>作用与 RuntimeVisibleAnnotations 属性类似，只不过作用对象为方法</td></tr><tr><td>RuntirmelnvisibleParameterAnniotation</td><td>方法表</td><td>作用与 RuntimelnvisibleAnnotations 属性类似，作用对象哪个为方法参数</td></tr><tr><td>AnnotationDefauit</td><td>方法表</td><td>用于记录注解类元素的默认值</td></tr><tr><td>BootstrapMethods</td><td>类文件</td><td>用于保存 invokeddynanic 指令引用的引导方式限定符</td></tr></tbody></table><h3 id="部分属性详解"><a href="#部分属性详解" class="headerlink" title="部分属性详解"></a>部分属性详解</h3><h4 id="①-ConstantValue属性"><a href="#①-ConstantValue属性" class="headerlink" title="① ConstantValue属性"></a>① ConstantValue属性</h4><p>ConstantValue属性表示一个常量字段的值。位于field_info结构的属性表中。</p><h4 id="②-Deprecated-属性"><a href="#②-Deprecated-属性" class="headerlink" title="② Deprecated 属性"></a>② Deprecated 属性</h4><p>Deprecated 属性是在JDK1.1为了支持注释中的关键词@deprecated而引入的。</p><h4 id="③-Code属性"><a href="#③-Code属性" class="headerlink" title="③ Code属性"></a>③ Code属性</h4><p>Code属性就是存放方法体里面的代码。但是，并非所有方法表都有Code属性。像接口或者抽象方法，他们没有具体的方法体，因此也就不会有Code属性了。Code属性表的结构，如下图：</p><table><thead><tr><th>类型</th><th>名称</th><th>数量</th><th>含义</th></tr></thead><tbody><tr><td>u2</td><td>attribute_name_index</td><td>1</td><td>属性名索引</td></tr><tr><td>u4</td><td>attribute_length</td><td>1</td><td>属性长度</td></tr><tr><td>u2</td><td>max_stack</td><td>1</td><td>操作数栈深度的最大值</td></tr><tr><td>u2</td><td>max_locals</td><td>1</td><td>局部变量表所需的存续空间</td></tr><tr><td>u4</td><td>code_length</td><td>1</td><td>字节码指令的长度</td></tr><tr><td>u1</td><td>code</td><td>code_lenth</td><td>存储字节码指令</td></tr><tr><td>u2</td><td>exception_table_length</td><td>1</td><td>异常表长度</td></tr><tr><td>exception_info</td><td>exception_table</td><td>exception_length</td><td>异常表</td></tr><tr><td>u2</td><td>attributes_count</td><td>1</td><td>属性集合计数器</td></tr><tr><td>attribute_info</td><td>attributes</td><td>attributes_count</td><td>属性集合</td></tr></tbody></table><p>可以看到：Code属性表的前两项跟属性表是一致的，即Code属性表遵循属性表的结构，后面那些则是他自定义的结构。</p><h4 id="④-InnerClasses-属性"><a href="#④-InnerClasses-属性" class="headerlink" title="④ InnerClasses 属性"></a>④ InnerClasses 属性</h4><p>为了方便说明特别定义一个表示类或接口的Class格式为C。如果C的常量池中包含某个CONSTANT_Class_info成员，且这个成员所表示的类或接口不属于任何一个包，那么C的ClassFile结构的属性表中就必须含有对应的InnerClasses属性。InnerClasses属性是在JDK1.1中为了支持内部类和内部接口而引入的，位于ClassFile结构的属性表。</p><h4 id="⑤-LineNumberTable属性"><a href="#⑤-LineNumberTable属性" class="headerlink" title="⑤ LineNumberTable属性"></a>⑤ LineNumberTable属性</h4><p>LineNumberTable属性是可选变长属性，位于Code结构的属性表。</p><p>LineNumberTable属性是用来描述Java源码行号与字节码行号之间的对应关系。这个属性可以用来在调试的时候定位代码执行的行数。</p><ul><li>start_pc，即字节码行号；</li><li>line_number，即Java源代码行号。</li></ul><p>在Code属性的属性表中，LineNumberTable属性可以按照任意顺序出现，此外，多个LineNumberTable属性可以共同表示一个行号在源文件中表示的内容，即LineNumberTable属性不需要与源文件的行一一对应。</p><h4 id="⑥-LocalVariableTable属性"><a href="#⑥-LocalVariableTable属性" class="headerlink" title="⑥ LocalVariableTable属性"></a>⑥ LocalVariableTable属性</h4><p>LocalVariableTable是可选变长属性，位于Code属性的属性表中。它被调试器用于确定方法在执行过程中局部变量的信息。在Code属性的属性表中，LocalVariableTable属性可以按照任意顺序出现。Code属性中的每个局部变量最多只能有一个LocalVariableTable属性。</p><ul><li><p>start pc + length表示这个变量在字节码中的生命周期起始和结束的偏移位置（this生命周期从头e到结尾10）</p></li><li><p>index就是这个变量在局部变量表中的槽位（槽位可复用）</p></li><li><p>name就是变量名</p></li><li><p>Descriptor表示局部变量类型描述</p></li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// LocalVariableTable属性表结构：</span>LocalVariableTable_attribute<span class="token punctuation">{</span>    u2 attribute_name_index<span class="token punctuation">;</span>    u4 attribute_length<span class="token punctuation">;</span>    u2 local_variable_table_length<span class="token punctuation">;</span>    <span class="token punctuation">{</span>        u2 start_pc<span class="token punctuation">;</span>        u2 length<span class="token punctuation">;</span>        u2 name_index<span class="token punctuation">;</span>        u2 descriptor_index<span class="token punctuation">;</span>        u2 index<span class="token punctuation">;</span>    <span class="token punctuation">}</span> local_variable_table<span class="token punctuation">[</span>local_variable_table_length<span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="⑦-Signature属性"><a href="#⑦-Signature属性" class="headerlink" title="⑦ Signature属性"></a>⑦ Signature属性</h4><p>Signature属性是可选的定长属性，位于ClassFile，field_info或method_info结构的属性表中。在Java语言中，任何类、接口、初始化方法或成员的泛型签名如果包含了类型变量（Type Variables）或参数化类型（Parameterized Types），则Signature属性会为它记录泛型签名信息。</p><p>⑧ SourceFile属性</p><table><thead><tr><th>类型</th><th>名称</th><th>数量</th><th>含义</th></tr></thead><tbody><tr><td>u2</td><td>attribute_name_index</td><td>1</td><td>属性名索引</td></tr><tr><td>u4</td><td>attribute_length</td><td>1</td><td>属性长度</td></tr><tr><td>u2</td><td>sourcefile index</td><td>1</td><td>源码文件素引</td></tr></tbody></table><p>可以看到，其长度总是固定的8个字节。</p><h4 id="⑨-其他属性"><a href="#⑨-其他属性" class="headerlink" title="⑨ 其他属性"></a>⑨ 其他属性</h4><p>Java虚拟机中预定义的属性有20多个，这里就不一一介绍了，通过上面几个属性的介绍，只要领会其精髓，其他属性的解读也是易如反掌。</p><h2 id="❷类加载时机"><a href="#❷类加载时机" class="headerlink" title="❷类加载时机"></a>❷类加载时机</h2><p><strong>主动引用</strong>：对于初始化阶段，虚拟机严格规范了有且只有 6 种情况下，必须对类进行初始化</p><ol><li>当遇到 <code>new</code>、<code>getstatic</code>、<code>putstatic</code>、<code>invokestatic</code> 这 4 条直接码指令时<ul><li>当 jvm 执行 <code>new</code> 指令时会初始化类。即当程序创建一个类的实例对象。</li><li>当 jvm 执行 <code>getstatic</code> 指令时会初始化类。即程序访问类的静态变量(不是静态常量，常量会被加载到运行时常量池)。</li><li>当 jvm 执行 <code>putstatic</code> 指令时会初始化类。即程序给类的静态变量赋值。</li><li>当 jvm 执行 <code>invokestatic</code> 指令时会初始化类。即程序调用类的静态方法。</li></ul></li><li>使用 <code>java.lang.reflect</code> 包的方法对类进行反射调用时如 <code>Class.forname(&quot;...&quot;)</code>, <code>newInstance()</code> 等等。如果类没初始化，需要触发其初始化。</li><li>初始化一个类，如果其父类还未初始化，则先触发该父类的初始化。</li><li>当虚拟机启动时，用户需要定义一个要执行的主类 (包含 <code>main</code> 方法的那个类)，虚拟机会先初始化这个类。</li><li><code>MethodHandle</code> 和 <code>VarHandle</code> 可以看作是轻量级的反射调用机制，而要想使用这 2 个调用， 就必须先使用 <code>findStaticVarHandle</code> 来初始化要调用的类。</li><li>当一个接口中定义了 JDK8 新加入的默认方法（被 default 关键字修饰的接口方法）时，如果有这个接口的实现类发生了初始化，那该接口要在其之前被初始化。</li></ol><p><strong>被动引用</strong>：所有引用类的方式都不会触发初始化，称为被动引用</p><ul><li>通过子类引用父类的静态字段，不会导致子类初始化，只会触发父类的初始化</li><li>通过数组定义来引用类，不会触发此类的初始化。该过程会对数组类进行初始化，数组类是一个由虚拟机自动生成的、直接继承自 Object 的子类，其中包含了数组的属性和方法</li><li>常量（final 修饰）在编译阶段会存入调用类的常量池中，本质上没有直接引用到定义常量的类，因此不会触发定义常量的类的初始化</li><li>调用 ClassLoader 类的 loadClass() 方法加载一个类，并不是对类的主动使用，不会导致类的初始化</li></ul><h2 id="❸类加载过程"><a href="#❸类加载过程" class="headerlink" title="❸类加载过程"></a>❸类加载过程</h2><h3 id="0-生命周期"><a href="#0-生命周期" class="headerlink" title="0.生命周期"></a>0.生命周期</h3><blockquote><p>在Java中数据类型分为<strong>基本数据类型</strong>和<strong>引用数据类型</strong>。基本数据类型由虚拟机预先定义，引用数据类型则需要进行类的加载。</p></blockquote><p>按照Java虚拟机规范，从class文件到加载到内存中的类，到类卸载出内存为止，它的整个生命周期包括如下7个阶段：</p><ul><li>加载（Loading）</li><li>链接：验证（Verification）、准备（Preparation）、解析（Resolution）</li><li>初始化（Initialization）</li><li>使用（Using）</li><li>卸载（Unloading）</li></ul><img src="https://img.jwt1399.top/img/202212241155714.png" style="zoom: 50%;" /><h3 id="1-加载"><a href="#1-加载" class="headerlink" title="1.加载"></a>1.加载</h3><p>加载过程完成以下三件事：</p><ol><li>通过全限定类名获取定义此类的二进制字节流</li><li>将字节流所代表的静态存储结构转换为方法区的运行时数据结构（Java类模型） </li><li>在内存中生成一个代表该类的 <code>Class</code> 对象，作为方法区这些数据的访问入口</li></ol><p>二进制字节流可以从以下方式中获取：</p><ul><li>通过文件系统读入一个class后缀的文件</li><li>从 ZIP 包读取，成为 JAR、EAR、WAR 格式的基础</li><li>从网络中获取，最典型的应用是 Applet</li><li>由其他文件生成，例如由 JSP 文件生成对应的 Class 类</li><li>运行时计算生成，例如动态代理技术，在 java.lang.reflect.Proxy 使用 ProxyGenerator.generateProxyClass 生成字节码</li></ul><p>方法区内部采用 C++ 的 instanceKlass 描述 Java 类的数据结构：</p><ul><li><code>_java_mirror</code> 即 Java 的类镜像，例如对 String 来说就是 String.class，作用是把 class 暴露给 Java 使用</li><li><code>_super</code> 即父类、<code>_fields</code> 即成员变量、<code>_methods</code> 即方法、<code>_constants</code> 即常量池、<code>_class_loader</code> 即类加载器、<code>_vtable</code> 虚方法表、<code>_itable</code> 接口方法表</li></ul><p>加载过程：</p><ul><li>如果这个类还有父类没有加载，先加载父类</li><li><code>Class 对象</code>和 <code>_java_mirror</code> 相互持有对方的地址，堆中对象通过 instanceKlass 和元空间进行交互</li><li>加载和链接可能是交替运行的</li></ul><table><thead><tr><th>类实例&amp;类模型位置</th><th>加载过程</th></tr></thead><tbody><tr><td><img src="https://img.jwt1399.top/img/202212241215535.png"></td><td><img src="https://img.jwt1399.top/img/202212241215552.png" style="zoom:80%;" /></td></tr></tbody></table><p><strong>数组类型不通过类加载器创建，它由 Java 虚拟机直接创建。</strong></p><p>创建数组类有些特殊，因为数组类本身并不是由类加载器负责创建，而是由 JVM 在运行时根据需要而直接创建的，但数组的元素类型仍然需要依靠类加载器去创建，创建数组类的过程：</p><ul><li>如果数组的元素类型是引用类型，那么遵循定义的加载过程递归加载和创建数组的元素类型</li><li>JVM 使用指定的元素类型和数组维度来创建新的数组类</li><li><strong>基本数据类型由启动类加载器加载</strong></li></ul><h3 id="2-链接"><a href="#2-链接" class="headerlink" title="2.链接"></a>2.链接</h3><h4 id="2-1验证"><a href="#2-1验证" class="headerlink" title="2.1验证"></a>2.1验证</h4><blockquote><p>确保 Class 文件的字节流中包含的信息是否符合 JVM 规范，保证被加载类的正确性，不会危害虚拟机自身的安全</p></blockquote><p>主要包括<strong>四种验证</strong>：</p><ul><li><p>文件格式验证</p><ul><li>魔数检查</li><li>版本检查</li><li>长度检查</li></ul></li><li><p>元数据验证</p><ul><li><p>是否所有的类都有父类的存在（除了 Object 外，其他类都应该有父类）</p></li><li><p>是否一些被定义为 final 的方法或者类被重写或继承了</p></li><li><p>非抽象类是否实现了所有抽象方法或者接口方法</p></li><li><p>是否存在不兼容的方法</p></li></ul></li><li><p>字节码验证</p><ul><li>在字节码的执行过程中，是否会跳转到一条不存在的指令</li><li>函数的调用是否传递了正确类型的参数</li><li>变量的赋值是不是给了正确的数据类型</li><li>栈映射帧（StackMapTable）在这个阶段用于检测在特定的字节码处，其局部变量表和操作数栈是否有着正确的数据类型</li></ul></li><li><p>符号引用验证</p><ul><li>Class 文件在其常量池会通过字符串记录将要使用的其他类或者方法</li><li>因此虚拟机就会检查这些类和方法是否存在，并且当前类有权限访问这些数据</li><li>如果一个需要使用类无法在系统中找到，则会抛出NoClassDefFoundError，如果一个方法无法被找到，则会抛出NoSuchMethodError。此阶段在解析环节才会执行。</li></ul></li></ul><h4 id="2-2准备"><a href="#2-2准备" class="headerlink" title="2.2准备"></a>2.2准备</h4><blockquote><p>为类变量&#x2F;静态变量分配内存并设置默认初始值的阶段，使用的是方法区的内存。</p></blockquote><p>说明：实例变量不会在这阶段分配内存，它会在对象实例化时随着对象一起被分配在堆中，类加载发生在所有实例化操作之前，并且类加载只进行一次，实例化可以进行多次</p><p>类变量初始化：</p><ul><li>static 变量分配空间和赋值是两个步骤：<strong>分配空间在准备阶段完成，赋值在初始化阶段完成</strong></li><li>如果 static 变量是 final 的基本类型以及字符串常量，那么编译阶段值（方法区）就确定了，准备阶段会显式初始化</li><li>如果 static 变量是 final 的，但属于引用类型或者构造器方法的字符串，赋值在初始化阶段完成</li></ul><p>实例：</p><ul><li><p>初始值一般为 0 值，例如下面的类变量 value 被初始化为 0 而不是 123：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">int</span> value <span class="token operator">=</span> <span class="token number">123</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre></li><li><p>常量 value 被初始化为 123 而不是 0：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> value <span class="token operator">=</span> <span class="token number">123</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre></li><li><p>Java 并不支持 boolean 类型，对于 boolean 类型，内部实现是 int，由于 int 的默认值是 0，故 boolean 的默认值就是 false</p></li></ul><table><thead><tr><th>类型</th><th>默认初始值</th></tr></thead><tbody><tr><td>byte</td><td>(byte)0</td></tr><tr><td>short</td><td>(short)0</td></tr><tr><td>int</td><td>0</td></tr><tr><td>long</td><td>0L</td></tr><tr><td>float</td><td>0.0f</td></tr><tr><td>double</td><td>0.0</td></tr><tr><td>char</td><td>\u0000</td></tr><tr><td>boolean</td><td>false</td></tr><tr><td>reference</td><td>null</td></tr></tbody></table><h4 id="2-3解析"><a href="#2-3解析" class="headerlink" title="2.3解析"></a>2.3解析</h4><blockquote><p>将常量池中类、接口、字段、方法的<strong>符号引用替换为直接引用</strong>（内存地址）的过程。</p></blockquote><ul><li>符号引用：用于描述目标。可以是任何字面量，属于编译原理方面的概念，如：包括类和接口的全限名、字段的名称和描述符、方法的名称和<strong>方法描述符</strong>（因为类还没有加载完，很多方法是找不到的）</li><li>直接引用：直接指向目标的地址。如果有了直接引用，那说明引用的目标必定已经存在于内存之中</li></ul><p>解析动作主要针对类或接口、字段、类方法、接口方法、方法类型等</p><ul><li>在类加载阶段解析的是非虚方法，静态绑定</li><li>也可以在初始化阶段之后再开始解析，这是为了支持 Java 的<strong>动态绑定</strong></li><li>通过解析操作，符号引用就可以转变为目标方法在类的虚方法表中的位置，从而使得方法被成功调用</li></ul><h3 id="3-初始化"><a href="#3-初始化" class="headerlink" title="3.初始化"></a>3.初始化</h3><blockquote><p>初始化阶段是执行初始化方法 <code>&lt;clinit&gt;()</code>方法的过程，是类加载的最后一步，这一步 JVM 才开始真正执行类中定义的 Java 程序代码(字节码)。</p></blockquote><p>在编译生成 class 文件时，编译器会产生两个方法加于 class 文件中，一个是类的初始化方法 <code>&lt;clinit&gt;()</code> ，另一个是实例的初始化方法 <code>&lt;init&gt;()</code> </p><p>类构造器 <code>&lt;clinit&gt;()</code> 与实例构造器 <code>&lt;init&gt;()</code> 不同，它不需要程序员进行显式调用，在一个类的生命周期中，类构造器最多被虚拟机<strong>调用一次</strong>，后续实例化不再加载，引用第一次加载的类，而实例构造器则会被虚拟机调用多次，只要程序员创建对象</p><h4 id="3-1-clinit"><a href="#3-1-clinit" class="headerlink" title="3.1 clinit"></a>3.1 clinit</h4><p><code>&lt;clinit&gt;()</code>：类构造器，由编译器自动收集类中<strong>所有类变量的赋值动作和静态语句块</strong>中的语句合并产生的</p><p>作用：是在类加载过程中的初始化阶段进行静态变量初始化和执行静态代码块</p><ul><li>如果类中没有静态变量或静态代码块，那么 <code>&lt;clinit&gt;()</code>  方法将不会被生成</li><li><code>&lt;clinit&gt;()</code>  方法只执行一次，在执行 <code>&lt;clinit&gt;()</code>  方法时，必须先执行父类的<code>&lt;clinit&gt;()</code> 方法</li><li>static 变量的赋值操作和静态代码块的合并顺序由源文件中出现的顺序决定</li><li>static 不加 final 的变量都在初始化环节赋值</li></ul><p><strong>线程安全</strong>问题：</p><ul><li>虚拟机会保证一个类的 <code>&lt;clinit&gt;()</code> 方法在多线程环境下被正确的加锁和同步，如果多个线程同时初始化一个类，只会有一个线程执行这个类的 <code>&lt;clinit&gt;()</code> 方法，其它线程都阻塞等待，直到活动线程执行 <code>&lt;clinit&gt;()</code> 方法完毕</li><li>如果在一个类的 <code>&lt;clinit&gt;()</code> 方法中有耗时的操作，就可能造成多个线程阻塞，在实际过程中此种阻塞很隐蔽</li></ul><p>特别注意：静态语句块只能访问到定义在它之前的类变量，定义在它之后的类变量只能赋值，不能访问</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Test</span> <span class="token punctuation">{</span>    <span class="token keyword">static</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">//i = 0;                // 给变量赋值可以正常编译通过</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">print</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment" spellcheck="true">// 这句编译器会提示“非法向前引用”</span>    <span class="token punctuation">}</span>    <span class="token keyword">static</span> <span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>接口中不可以使用静态语句块，但有类变量初始化的赋值操作，因此接口与类一样都会生成 <code>&lt;clinit&gt;()</code> 方法</p><ul><li>在初始化一个接口时，并不会先初始化它的父接口，所以执行接口的 <code>&lt;clinit&gt;()</code> 方法不需要先执行父接口的 <code>&lt;clinit&gt;()</code> 方法</li><li>在初始化一个实现类时，不会先初始化所实现的接口，所以接口的实现类在初始化时不会执行接口的 <code>&lt;clinit&gt;()</code> 方法</li><li>只有当父接口中定义的变量使用时，父接口才会初始化</li></ul><h4 id="3-3-init"><a href="#3-3-init" class="headerlink" title="3.3 init"></a>3.3 init</h4><p><code>&lt;init&gt;()</code> 指实例构造器，主要作用是在类实例化过程中执行，执行内容包括成员变量初始化和代码块的执行</p><p>实例化即调用 <code>&lt;init&gt;()V</code> ，虚拟机会保证这个类的构造方法的线程安全，先为实例变量分配内存空间，再执行赋默认值，然后根据源码中的顺序执行赋初值或代码块，没有成员变量初始化和代码块则不会执行</p><p>new 关键字会创建对象并复制 dup 一个对象引用，一个调用 <code>&lt;init&gt;</code> 方法，另一个用来赋值给接收者</p><h3 id="4-卸载"><a href="#4-卸载" class="headerlink" title="4.卸载"></a>4.卸载</h3><blockquote><p>卸载类即该类的 Class 对象被 GC。</p></blockquote><p>卸载类需要满足3个要求:</p><ol><li>该类的所有的实例对象都已被 GC，也就是说堆不存在该类的实例对象</li><li>该类没有在其他任何地方被引用</li><li>该类的类加载器的实例已被 GC</li></ol><ul><li><p>JVM 自带的类加载器加载的类是不会被卸载的</p><ul><li>因为 JVM 会始终引用启动、扩展、系统类加载器，这些类加载器始终引用它们所加载的类，这些类始终是可及的</li></ul></li><li><p>自定义的类加载器加载的类是可能被卸载。</p></li></ul><h2 id="❹类实例化过程"><a href="#❹类实例化过程" class="headerlink" title="❹类实例化过程"></a>❹类实例化过程</h2><p><strong>父类的类构造器<code>&lt;clinit&gt;</code> ➔ 子类的类构造器<code>&lt;clinit&gt;</code>➔ 父类的实例构造器<code>&lt;init&gt;</code> ➔ 父类的构造函数 ➔ 子类的的实例构造器<code>&lt;init&gt;</code> ➔ 子类的构造函数</strong></p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">//父类</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Father</span> <span class="token punctuation">{</span>    <span class="token keyword">static</span> <span class="token punctuation">{</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"父静态代码块"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token punctuation">{</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"父非静态代码块"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">public</span> <span class="token function">Father</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"父构造器"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">//子类</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Son</span> <span class="token keyword">extends</span> <span class="token class-name">Father</span> <span class="token punctuation">{</span>    <span class="token keyword">static</span> <span class="token punctuation">{</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"子静态代码块"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token punctuation">{</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"子非静态代码块"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">public</span> <span class="token function">Son</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"子构造器"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>      <span class="token comment" spellcheck="true">//创建对象</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">new</span> <span class="token class-name">Son</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">/**  运行结果：  父静态代码块  子静态代码块  父非静态代码块  父构造器  子非静态代码块  子构造器**/</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="❺类加载器"><a href="#❺类加载器" class="headerlink" title="❺类加载器"></a>❺类加载器</h2><h3 id="0-基础知识"><a href="#0-基础知识" class="headerlink" title="0.基础知识"></a>0.基础知识</h3><h4 id="类加载分类"><a href="#类加载分类" class="headerlink" title="类加载分类"></a>类加载分类</h4><ul><li><p>显式加载：在代码中通过调用ClassLoader加载class对象，如直接使用Class.forName(name)或ClassLoader.loadClass(name)加载class对象。</p><ul><li><p>ClassLoader.loadClass(className)：只加载和链接，<strong>不会进行初始化</strong></p></li><li><p>Class.forName(String name, boolean initialize, ClassLoader loader)：使用 loader 进行加载和链接，根据参数 initialize 决定是否初始化</p></li></ul></li></ul><ul><li>隐式加载：不直接在代码中调用 ClassLoader 的方法加载类对象，如在加载某个类的class文件时，该类的class文件中引用了另外一个类的对象，此时额外引用的类将通过JVM自动加载到内存中。<ul><li>创建类对象、使用类的静态域、创建子类对象、使用子类的静态域</li><li>在 JVM 启动时，通过三大类加载器加载 class</li></ul></li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">//隐式加载</span>User user <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">User</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//显式加载，并初始化</span>Class <span class="token class-name">clazz</span> <span class="token operator">=</span> Class<span class="token punctuation">.</span><span class="token function">forName</span><span class="token punctuation">(</span><span class="token string">"com.test.java.User"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//显式加载，但不初始化</span>ClassLoader<span class="token punctuation">.</span><span class="token function">getSystemClassLoader</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">loadClass</span><span class="token punctuation">(</span><span class="token string">"com.test.java.Parent"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>类加载器基本特征：</p><ul><li><strong>可见性</strong>，子类加载器可以访问父加载器加载的类型，但是反过来是不允许的</li><li><strong>单一性</strong>，由于父加载器的类型对于子加载器是可见的，所以父加载器中加载过的类型，不会在子加载器中重复加载</li></ul><h4 id="ClassLoader"><a href="#ClassLoader" class="headerlink" title="ClassLoader"></a>ClassLoader</h4><blockquote><p>ClassLoader 类，是一个抽象类，除启动类加载器外其它类加载器都继承自 ClassLoader</p></blockquote><p>获取 ClassLoader 的途径：</p><ul><li>获取当前类的 ClassLoader：<code>clazz.getClassLoader()</code></li><li>获取当前线程上下文的 ClassLoader：<code>Thread.currentThread.getContextClassLoader()</code></li><li>获取系统的 ClassLoader：<code>ClassLoader.getSystemClassLoader()</code></li><li>获取调用者的 ClassLoader：<code>DriverManager.getCallerClassLoader()</code></li></ul><p>ClassLoader 类常用方法：</p><ul><li><code>getParent()</code>：返回该类加载器的超类加载器  </li><li><code>loadclass(String name)</code>：加载名为name的类，返回结果为Class类的实例，<strong>该方法就是双亲委派模式</strong></li><li><code>findclass(String name)</code>：查找二进制名称为 name 的类，返回结果为 Class 类的实例，该方法会在检查完父类加载器之后被 loadClass() 方法调用</li><li><code>findLoadedClass(String name)</code>：查找名称为 name 的已经被加载过的类，final 修饰无法重写</li><li><code>defineClass(String name, byte[] b, int off, int len)</code>：将<strong>字节流</strong>解析成 JVM 能够识别的类对象</li><li><code>resolveclass(Class&lt;?&gt; c)</code>：链接指定的 Java 类，可以使类的 Class 对象创建完成的同时也被解析</li><li><code>InputStream getResourceAsStream(String name)</code>：指定资源名称获取输入流</li></ul><h4 id="类加载模型"><a href="#类加载模型" class="headerlink" title="类加载模型"></a>类加载模型</h4><p>在 JVM 中，对于类加载模型提供了三种，分别为全盘加载、双亲委派、缓存机制</p><ul><li><p><strong>全盘加载：</strong>当一个类加载器负责加载某个 Class 时，该 Class 所依赖和引用的其他 Class 也将由该类加载器负责载入，除非显示指定使用另外一个类加载器来载入</p></li><li><p><strong>双亲委派：</strong>某个特定的类加载器在接到加载类的请求时，首先将加载任务委托给父加载器，<strong>依次递归</strong>，如果父加载器可以完成类加载任务，就成功返回；只有当父加载器无法完成此加载任务时，才自己去加载</p></li><li><p><strong>缓存机制：</strong>会保证所有加载过的 Class 都会被缓存，当程序中需要使用某个 Class 时，类加载器先从缓存区中搜寻该 Class，只有当缓存区中不存在该 Class 对象时，系统才会读取该类对应的二进制数据，并将其转换成 Class 对象存入缓冲区（方法区）中。<strong>这就是修改了 Class 后，必须重新启动 JVM，程序所做的修改才会生效的原因</strong></p></li></ul><h3 id="1-加载器"><a href="#1-加载器" class="headerlink" title="1.加载器"></a>1.加载器</h3><blockquote><p>类加载器是 Java 的核心组件，用于加载字节码到 JVM 内存，得到 Class 类的对象</p></blockquote><p>从 Java 虚拟机规范来讲，只存在以下两种不同的类加载器：</p><ul><li>启动类加载器（Bootstrap ClassLoader）：使用 C++ 实现，是虚拟机自身的一部分</li><li>自定义类加载器（User-Defined ClassLoader）：Java 虚拟机规范<strong>将所有派生于抽象类 ClassLoader 的类加载器都划分为自定义类加载器</strong>，使用 Java 语言实现，独立于虚拟机</li></ul><p>从 Java 开发人员的角度看：</p><img src="https://img.jwt1399.top/img/202301201331894.png" style="zoom: 33%;" /><ul><li>启动类加载器（Bootstrap ClassLoader）：<ul><li>出于安全考虑，Bootstrap 启动类加载器只加载包名为 java、javax、sun 等开头的类</li><li>启动类加载器负责加载在 <code>JAVA_HOME/jre/lib</code> 或 <code>sun.boot.class.path</code> 目录中的，或者被 <code>-Xbootclasspath</code> 参数所指定的路径中的类，并且是虚拟机识别的类库加载到虚拟机内存中</li><li>仅按照文件名识别，如 rt.jar 名字不符合的类库即使放在 lib 目录中也不会被加载</li><li>启动类加载器无法被 Java 程序直接引用，编写自定义类加载器时，如果要把加载请求委派给启动类加载器，直接使用 null 代替</li></ul></li><li>扩展类加载器（Extension ClassLoader）：<ul><li>由 <code>ExtClassLoader (sun.misc.Launcher$ExtClassLoader)</code>  实现，上级为 Bootstrap，显示为 null</li><li>将 <code>JAVA_HOME/jre/lib/ext</code> 或者被 <code>java.ext.dir</code> 系统变量所指定路径中的所有类库加载到内存中</li><li>开发者可以使用扩展类加载器，创建的 JAR 放在此目录下，会由扩展类加载器自动加载</li></ul></li><li>应用程序类加载器（Application ClassLoader）：<ul><li>由 <code>AppClassLoader(sun.misc.Launcher$AppClassLoader)</code> 实现，上级为 Extension</li><li>负责加载环境变量 classpath 或系统属性 <code>java.class.path</code> 指定路径下的类库</li><li>这个类加载器是 ClassLoader 中的 <code>getSystemClassLoader()</code> 方法的返回值，因此称为系统类加载器</li><li>可以直接使用这个类加载器，如果应用程序中没有自定义类加载器，这个就是程序中默认的类加载器</li></ul></li><li>自定义类加载器：由开发人员自定义的类加载器，上级是 Application</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ClassLoaderTest</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">//获取系统类加载器</span>        ClassLoader systemClassLoader <span class="token operator">=</span> ClassLoader<span class="token punctuation">.</span><span class="token function">getSystemClassLoader</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>systemClassLoader<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//sun.misc.Launcher$AppClassLoader@18b4aac2</span>        <span class="token comment" spellcheck="true">//获取其上层  扩展类加载器</span>        ClassLoader extClassLoader <span class="token operator">=</span> systemClassLoader<span class="token punctuation">.</span><span class="token function">getParent</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>extClassLoader<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//sun.misc.Launcher$ExtClassLoader@610455d6</span>        <span class="token comment" spellcheck="true">//获取其上层 获取不到启动类加载器</span>        ClassLoader bootStrapClassLoader <span class="token operator">=</span> extClassLoader<span class="token punctuation">.</span><span class="token function">getParent</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>bootStrapClassLoader<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//null</span>        <span class="token comment" spellcheck="true">//对于用户自定义类来说：使用系统类加载器进行加载</span>        ClassLoader classLoader <span class="token operator">=</span> ClassLoaderTest<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">.</span><span class="token function">getClassLoader</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>classLoader<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//sun.misc.Launcher$AppClassLoader@18b4aac2</span>        <span class="token comment" spellcheck="true">//String 类使用引导类加载器进行加载的 --> java核心类库都是使用启动类加载器加载的</span>        ClassLoader classLoader1 <span class="token operator">=</span> String<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">.</span><span class="token function">getClassLoader</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>classLoader1<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//null</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="2-双亲委派"><a href="#2-双亲委派" class="headerlink" title="2.双亲委派"></a>2.双亲委派</h3><ul><li><strong>向上委托</strong>：如果一个类加载器收到了类加载请求，它并不会自己先去加载，而是把这个请求委托给父类的加载器去执行，如果父类加载器还存在其父类加载器，则进一步向上委托，依次递归，请求最终将到达顶层的启动类加载器。如果父类加载器可以完成类加载任务，就成功返回；</li><li><strong>向下委派</strong>：倘若父类加载器无法完成此加载任务，子加载器才会尝试自己去加载，这就是双亲委派模式。</li></ul><img src="https://img.jwt1399.top/img/202212241706954.png" style="zoom:50%;" /><p>双亲委派机制的优点：</p><ul><li><p>可以避免类被重复加载，当父类已经加载后则无需重复加载，保证类的全局唯一性</p></li><li><p>保护程序安全，防止类库的核心 API 被随意篡改</p><p>例如：在工程中新建 java.lang 包，接着在该包下新建 String 类，并定义 main 函数</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">String</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"demo info"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>此时执行 main 函数会出现异常，在类 java.lang.String 中找不到 main 方法。因为双亲委派的机制，java.lang.String 在启动类加载器（Bootstrap）得到加载，启动类加载器优先级更高，在核心 jre 库中有其相同名字的类文件，但该类中并没有 main 方法</p></li></ul><p>双亲委派机制的缺点：</p><p>检查类是否加载的委托过程是单向的，这个方式虽然从结构上看比较清晰，使各个 ClassLoader 的职责非常明确，但<strong>顶层的 ClassLoader 无法访问底层的 ClassLoader 所加载的类</strong>（可见性）</p><p>通常情况下，启动类加载器中的类为<u>系统核心类</u>，包括一些重要的系统接口，而在应用类加载器中，为<u>应用类</u>。按照这种模式，应用类访问系统类自然是没有问题，但是系统类访问应用类就会出现问题。</p><p>参考：<a href="https://m.imooc.com/wiki/jvm-loadparent">https://m.imooc.com/wiki/jvm-loadparent</a></p><h3 id="3-源码分析"><a href="#3-源码分析" class="headerlink" title="3.源码分析"></a>3.源码分析</h3><p>双亲委派机制在<code>java.lang.ClassLoader.loadClass(String，boolean)</code>方法中体现。逻辑如下：</p><ol><li><p>先在当前加载器的缓存中查找有无目标类 <code>findLoadedClass(name)</code>，如果有，直接返回。</p></li><li><p>判断当前加载器的父加载器是否为空，如果不为空，则调用<code>parent.loadClass(name，false)</code>接口进行加载。</p></li><li><p>如果当前加载器的父加载器为空，则调用<code>findBootstrapClassorNull(name)</code>接口，让启动类加载器进行加载。</p></li><li><p>如果通过以上3条路径都没能成功加载，则调用<code>findClass(name)</code>接口进行加载。该接口最终会调用<code>java.lang.ClassLoader</code>接口的<code>defineClass</code>系列的<code>native</code>接口加载目标Java类。</p></li></ol><p>双亲委派的模型就隐藏在这第2和第3步中。</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">protected</span> Class<span class="token operator">&lt;</span><span class="token operator">?</span><span class="token operator">></span> <span class="token function">loadClass</span><span class="token punctuation">(</span>String name<span class="token punctuation">,</span> <span class="token keyword">boolean</span> resolve<span class="token punctuation">)</span>    <span class="token keyword">throws</span> ClassNotFoundException <span class="token punctuation">{</span>    <span class="token keyword">synchronized</span> <span class="token punctuation">(</span><span class="token function">getClassLoadingLock</span><span class="token punctuation">(</span>name<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>       <span class="token comment" spellcheck="true">// 1.调用当前类加载器的 findLoadedClass(name)，检查当前类加载器是否已加载过指定 name 的类</span>        Class <span class="token class-name">c</span> <span class="token operator">=</span> <span class="token function">findLoadedClass</span><span class="token punctuation">(</span>name<span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token comment" spellcheck="true">// 当前类加载器如果没有加载过</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>c <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token keyword">long</span> t0 <span class="token operator">=</span> System<span class="token punctuation">.</span><span class="token function">nanoTime</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">try</span> <span class="token punctuation">{</span>                <span class="token comment" spellcheck="true">// 2.判断当前类加载器是否有父类加载器</span>                <span class="token keyword">if</span> <span class="token punctuation">(</span>parent <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>                    <span class="token comment" spellcheck="true">// 如果当前类加载器有父类加载器，则调用父类加载器的 loadClass(name,false)</span>              <span class="token comment" spellcheck="true">// 父类加载器的 loadClass 方法，又会检查自己是否已经加载过</span>                    c <span class="token operator">=</span> parent<span class="token punctuation">.</span><span class="token function">loadClass</span><span class="token punctuation">(</span>name<span class="token punctuation">,</span> <span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>                    <span class="token comment" spellcheck="true">// 3.当前类加载器没有父类加载器，说明当前类加载器是 BootStrapClassLoader</span>             <span class="token comment" spellcheck="true">// 则调用 BootStrap ClassLoader 的方法加载类</span>                    c <span class="token operator">=</span> <span class="token function">findBootstrapClassOrNull</span><span class="token punctuation">(</span>name<span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token punctuation">}</span>            <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">ClassNotFoundException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span>c <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token keyword">long</span> t1 <span class="token operator">=</span> System<span class="token punctuation">.</span><span class="token function">nanoTime</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token comment" spellcheck="true">// 4.如果调用父类的类加载器无法对类进行加载，则用自己的 findClass() 方法进行加载</span>                <span class="token comment" spellcheck="true">// 可以自定义 findClass() 方法</span>                c <span class="token operator">=</span> <span class="token function">findClass</span><span class="token punctuation">(</span>name<span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token comment" spellcheck="true">// this is the defining class loader; record the stats</span>                sun<span class="token punctuation">.</span>misc<span class="token punctuation">.</span>PerfCounter<span class="token punctuation">.</span><span class="token function">getParentDelegationTime</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">addTime</span><span class="token punctuation">(</span>t1 <span class="token operator">-</span> t0<span class="token punctuation">)</span><span class="token punctuation">;</span>                sun<span class="token punctuation">.</span>misc<span class="token punctuation">.</span>PerfCounter<span class="token punctuation">.</span><span class="token function">getFindClassTime</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">addElapsedTimeFrom</span><span class="token punctuation">(</span>t1<span class="token punctuation">)</span><span class="token punctuation">;</span>                sun<span class="token punctuation">.</span>misc<span class="token punctuation">.</span>PerfCounter<span class="token punctuation">.</span><span class="token function">getFindClasses</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">increment</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>resolve<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 链接指定的 Java 类，可以使类的 Class 对象创建完成的同时也被解析</span>            <span class="token function">resolveClass</span><span class="token punctuation">(</span>c<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> c<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="4-破坏委派"><a href="#4-破坏委派" class="headerlink" title="4.破坏委派"></a>4.破坏委派</h3><blockquote><p>双亲委派模型并不是一个具有强制性约束的模型，而是 Java 设计者推荐给开发者的类加载器实现方式</p></blockquote><p>破坏双亲委派模型的方式：</p><ul><li><p>1.自定义 ClassLoader</p><ul><li>如果不想破坏双亲委派模型，只需要重写 findClass 方法</li><li>如果想要去破坏双亲委派模型，需要去**重写 loadClass **方法</li></ul></li><li><p>2.引入线程上下文类加载器</p><p>Java 提供了很多服务提供者接口（Service Provider Interface，SPI），允许第三方为这些接口提供实现。常见的有 JDBC、JCE、JNDI 等。这些 SPI 接口由 Java 核心库来提供，而 SPI 的实现代码则是作为 Java 应用所依赖的 jar 包被包含进类路径 classpath 里，SPI 接口中的代码需要加载具体的实现类：</p><ul><li>SPI 的接口是 Java 核心库的一部分，是由启动类加载器来加载的</li><li>SPI 的实现类是由系统类加载器加载，启动类加载器是无法找到 SPI 的实现类，因为双亲委派模型中 BootstrapClassloader 无法委派 AppClassLoader 来加载类</li></ul><p>JDK 开发人员引入了线程上下文类加载器（Thread Context ClassLoader），这种类加载器可以通过 Thread  类的 setContextClassLoader 方法设置线程上下文类加载器，在执行线程中抛弃双亲委派加载模式，使程序可以逆向使用类加载器，使 Bootstrap 加载器拿到了 Application 加载器加载的类，破坏了双亲委派模型</p></li><li><p>3.实现程序的动态性，如代码热替换（Hot Swap）、模块热部署（Hot Deployment）</p><p>IBM 公司主导的 JSR一291（OSGiR4.2）实现模块化热部署的关键是它自定义的类加载器机制的实现，每一个程序模块（OSGi 中称为 Bundle）都有一个自己的类加载器，当更换一个 Bundle 时，就把 Bundle 连同类加载器一起换掉以实现代码的热替换，在 OSGi 环境下，类加载器不再双亲委派模型推荐的树状结构，而是进一步发展为更加复杂的网状结构</p><p>当收到类加载请求时，OSGi 将按照下面的顺序进行类搜索:</p><ol><li>将以 java.* 开头的类，委派给父类加载器加载</li><li>否则，将委派列表名单内的类，委派给父类加载器加载</li><li>否则，将 Import 列表中的类，委派给 Export 这个类的 Bundle 的类加载器加载</li><li>否则，查找当前 Bundle 的 ClassPath，使用自己的类加载器加载</li><li>否则，查找类是否在自己的 Fragment Bundle 中，如果在就委派给 Fragment Bundle 类加载器加载</li><li>否则，查找 Dynamic Import 列表的 Bundle，委派给对应 Bundle 的类加载器加载</li><li>否则，类查找失败</li></ol><p>热替换是指在程序的运行过程中，不停止服务，只通过替换程序文件来修改程序的行为，<strong>热替换的关键需求在于服务不能中断</strong>，修改必须立即表现正在运行的系统之中</p></li></ul><img src="https://img.jwt1399.top/img/202212251522492.png" style="zoom: 33%;" /><h3 id="5-自定义加载器"><a href="#5-自定义加载器" class="headerlink" title="5.自定义加载器"></a>5.自定义加载器</h3><p>对于自定义类加载器的实现，只需要继承 ClassLoader 类，覆写 findClass 方法即可</p><p>作用：隔离加载类、修改类加载的方式、拓展加载源、防止源码泄漏</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">//自定义类加载器，读取指定的类路径classPath下的class文件</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">MyClassLoader</span> <span class="token keyword">extends</span> <span class="token class-name">ClassLoader</span><span class="token punctuation">{</span>    <span class="token keyword">private</span> String classPath<span class="token punctuation">;</span>    <span class="token keyword">public</span> <span class="token function">MyClassLoader</span><span class="token punctuation">(</span>String classPath<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">this</span><span class="token punctuation">.</span>classPath <span class="token operator">=</span> classPath<span class="token punctuation">;</span>    <span class="token punctuation">}</span>         <span class="token keyword">public</span> <span class="token function">MyClassLoader</span><span class="token punctuation">(</span>ClassLoader parent<span class="token punctuation">,</span> String classPath<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">super</span><span class="token punctuation">(</span>parent<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">this</span><span class="token punctuation">.</span>classPath <span class="token operator">=</span> classPath<span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">protected</span> Class<span class="token operator">&lt;</span><span class="token operator">?</span><span class="token operator">></span> <span class="token function">findClass</span><span class="token punctuation">(</span>String className<span class="token punctuation">)</span> <span class="token keyword">throws</span> ClassNotFoundException <span class="token punctuation">{</span>       BufferedInputStream bis <span class="token operator">=</span> null<span class="token punctuation">;</span>        ByteArrayOutputStream baos <span class="token operator">=</span> null<span class="token punctuation">;</span>        <span class="token keyword">try</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 获取字节码文件的完整路径</span>            String fileName <span class="token operator">=</span> classPath <span class="token operator">+</span> className <span class="token operator">+</span> <span class="token string">".class"</span><span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">// 获取一个输入流</span>            bis <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">BufferedInputStream</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">FileInputStream</span><span class="token punctuation">(</span>fileName<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">// 获取一个输出流</span>            baos <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ByteArrayOutputStream</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">// 具体读入数据并写出的过程</span>            <span class="token keyword">int</span> len<span class="token punctuation">;</span>            <span class="token keyword">byte</span><span class="token punctuation">[</span><span class="token punctuation">]</span> data <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">byte</span><span class="token punctuation">[</span><span class="token number">1024</span><span class="token punctuation">]</span><span class="token punctuation">;</span>            <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>len <span class="token operator">=</span> bis<span class="token punctuation">.</span><span class="token function">read</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">!=</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                baos<span class="token punctuation">.</span><span class="token function">write</span><span class="token punctuation">(</span>data<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> len<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>            <span class="token comment" spellcheck="true">// 获取内存中的完整的字节数组的数据</span>            <span class="token keyword">byte</span><span class="token punctuation">[</span><span class="token punctuation">]</span> byteCodes <span class="token operator">=</span> baos<span class="token punctuation">.</span><span class="token function">toByteArray</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">// 调用 defineClass()，将字节数组的数据转换为 Class 的实例。</span>            Class <span class="token class-name">clazz</span> <span class="token operator">=</span> <span class="token function">defineClass</span><span class="token punctuation">(</span>null<span class="token punctuation">,</span> byteCodes<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> byteCodes<span class="token punctuation">.</span>length<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">return</span> clazz<span class="token punctuation">;</span>        <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">IOException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span>            e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>            <span class="token keyword">try</span> <span class="token punctuation">{</span>                <span class="token keyword">if</span> <span class="token punctuation">(</span>baos <span class="token operator">!=</span> null<span class="token punctuation">)</span>                    baos<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">IOException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span>                e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>            <span class="token keyword">try</span> <span class="token punctuation">{</span>                <span class="token keyword">if</span> <span class="token punctuation">(</span>bis <span class="token operator">!=</span> null<span class="token punctuation">)</span>                    bis<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">IOException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span>                e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> null<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>    MyClassLoader loader <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">MyClassLoader</span><span class="token punctuation">(</span><span class="token string">"/Workspace/Project/JVM_study/src/java1/"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">try</span> <span class="token punctuation">{</span>        Class <span class="token class-name">clazz</span> <span class="token operator">=</span> loader<span class="token punctuation">.</span><span class="token function">loadClass</span><span class="token punctuation">(</span><span class="token string">"Demo1"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"加载此类的类的加载器为："</span> <span class="token operator">+</span> clazz<span class="token punctuation">.</span><span class="token function">getClassLoader</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getClass</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//MyClassLoader</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"加载当前类的类的加载器的父类加载器为："</span> <span class="token operator">+</span> clazz<span class="token punctuation">.</span><span class="token function">getClassLoader</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getParent</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getClass</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//sun.misc.Launcher$AppClassLoader</span>    <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">ClassNotFoundException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span>        e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="6-JDK9新特性"><a href="#6-JDK9新特性" class="headerlink" title="6.JDK9新特性"></a>6.JDK9新特性</h3><p>为了保证兼容性，JDK9 没有改变三层类加载器架构和双亲委派模型，但为了模块化系统的顺利运行做了一些变动：</p><ul><li><p>扩展机制被移除，扩展类加载器由于<strong>向后兼容性</strong>的原因被保留，不过被重命名为平台类加载器（platform classloader），可以通过 ClassLoader 的新方法 getPlatformClassLoader() 来获取</p></li><li><p>JDK9 基于模块化进行构建（原来的 rt.jar 和 tools.jar 被拆分成数个 JMOD 文件），其中 Java 类库就满足了可扩展的需求，那就无须再保留 <code>&lt;JAVA_HOME&gt;\lib\ext</code> 目录，此前使用这个目录或者 <code>java.ext.dirs</code> 系统变量来扩展 JDK 功能的机制就不需要再存在</p></li><li><p>启动类加载器、平台类加载器、应用程序类加载器全都继承于 <code>jdk.internal.loader.BuiltinClassLoader</code></p></li></ul><h1 id="⑤程序编译"><a href="#⑤程序编译" class="headerlink" title="⑤程序编译"></a>⑤程序编译</h1><blockquote><p>Java 代码执行流程：<code>Java 程序(.java) --（编译）--&gt; 字节码文件(.class)--（解释执行/JIT）--&gt; 操作系统（Win，Linux）</code></p></blockquote><h2 id="❶字节码指令集"><a href="#❶字节码指令集" class="headerlink" title="❶字节码指令集"></a>❶字节码指令集</h2><p>Java 字节码由操作码和操作数组成。</p><ul><li>操作码（Opcode）：一个字节长度（0-255，意味着指令集的操作码总数不可能超过 256 条），代表着某种特定的操作含义。</li><li>操作数（Operands）：零个或者多个，紧跟在操作码之后，代表此操作需要的参数。</li></ul><p>由于 Java 虚拟机是基于栈而不是寄存器的结构，所以大多数指令都只有一个操作码。比如 <code>aload_0</code>（将局部变量表中下标为 0 的数据压入操作数栈中）就只有操作码没有操作数，而 <code>invokespecial #1</code>（调用成员方法或者构造方法，并传递常量池中下标为 1 的常量）就是由操作码和操作数组成的。</p><h3 id="0-字节码与数据类型"><a href="#0-字节码与数据类型" class="headerlink" title="0.字节码与数据类型"></a>0.字节码与数据类型</h3><p>在 JVM 的指令集中，大多数的指令都包含了其操作所对应的数据类型信息。例如 iload 指令用于从局部变量表中加载 int 型的数据到操作数栈中，而 fload 指令加载的则是 float 类型的数据</p><ul><li>i 代表对 int 类型的数据操作</li><li>l 代表 long </li><li>s 代表 short</li><li>b 代表 byte</li><li>c 代表 char</li><li>f 代表 float</li><li>d 代表 double</li><li>a代表引用类型</li></ul><p>大部分的指令都没有支持 byte、char、short、boolean 类型，编译器会在编译期或运行期将 byte 和 short 类型的数据带符号扩展（Sign-Extend-）为相应的 int 类型数据，将 boolean 和 char 类型数据零位扩展（Zero-Extend）为相应的 int 类型数据</p><p>在做值相关操作时:</p><ul><li>一个指令，可以从局部变量表、常量池、堆中对象、方法调用、系统调用中等取得数据，这些数据（可能是值，也可能是对象的引用）被压入操作数栈</li><li>一个指令，也可以从操作数栈中取出一到多个值（pop 多次），完成赋值、加减乘除、方法传参、系统调用等等操作</li></ul><h3 id="1-加载与存储指令"><a href="#1-加载与存储指令" class="headerlink" title="1.加载与存储指令"></a>1.加载与存储指令</h3><blockquote><p>加载和存储指令用于将数据从栈帧的局部变量表和操作数栈之间来回传递</p></blockquote><p><strong>局部变量压栈指令</strong>：将给定的局部变量表中的数据压入操作数栈</p><ul><li><code>xload</code>、<code>xload_n</code>，x 表示数据类型，为 i、l、f、d、a； n 为 0 到 3</li><li>指令 <code>xload_n</code> 表示将第 n 个局部变量压入操作数栈，aload_n 表示将一个对象引用压栈</li><li>指令 <code>xload n</code> 通过指定参数的形式，把局部变量压入操作数栈，局部变量数量超过 4 个时使用这个命令</li></ul><p><strong>常量入栈指令</strong>：将常数压入操作数栈，根据数据类型和入栈内容的不同，又分为 <code>const_&lt;n&gt;</code>、<code>push</code>、<code>ldc</code> 指令</p><ul><li><code>push</code>：包括 bipush 和 sipush，区别在于接收数据类型的不同，bipush 接收 8 位整数作为参数，sipush 接收 16 位整数</li><li><code>ldc</code>：如果以上指令不能满足需求，可以使用 ldc 指令，接收一个 8 位的参数，该参数指向常量池中的 int、 float 或者 String 的索引，将指定的内容压入堆栈。<code>ldc_w</code> 接收两个 8 位参数，能支持的索引范围更大，如果要压入的元素是 long 或 double 类型的，则使用 <code>ldc2_w</code> 指令</li><li><code>aconst_null</code> 将 null 对象引用压入栈，<code>iconst_m1</code> 将 int 类型常量 -1 压入栈，<code>iconst_0</code> 将 int 类型常量 0 压入栈</li></ul><p><strong>出栈装入局部变量表指令</strong>：将操作数栈中栈顶元素弹出后，装入局部变量表的指定位置，用于给局部变量赋值</p><ul><li><code>xstore</code>、<code>xstore_n</code>，x 表示取值类型为 i、l、f、d、a， n 为 0 到 3</li><li><code>xastore</code> 表示存入数组，x 取值为 i、l、f、d、a、b、c、s</li></ul><p><strong>扩充局部变量表的访问索引的指令</strong>：<code>wide</code></p><h3 id="2-算术指令"><a href="#2-算术指令" class="headerlink" title="2.算术指令"></a>2.算术指令</h3><blockquote><p>算术指令用于对两个操作数栈上的值进行某种特定运算，并把计算结果重新压入操作数栈</p></blockquote><p>没有直接支持 byte、 short、 char 和 boolean 类型的算术指令，对于这些数据的运算，都使用 int 类型的指令来处理，数组类型也是转换成 int 数组</p><ul><li><p>加法指令：iadd、ladd、fadd、dadd</p></li><li><p>减法指令：isub、lsub、fsub、dsub</p></li><li><p>乘法指令：imu、lmu、fmul、dmul</p></li><li><p>除法指令：idiv、ldiv、fdiv、ddiv</p></li><li><p>求余指令：irem、lrem、frem、drem（remainder 余数）</p></li><li><p>取反指令：ineg、lneg、fneg、dneg （negation 取反）</p></li><li><p>自增指令：iinc 1 1（直接<strong>在局部变量 slot 上进行运算</strong>，不用放入操作数栈）</p></li><li><p>自减指令：iinc 1 -1 </p></li><li><p>位运算指令，又可分为：</p><ul><li>位移指令：ishl、ishr、 iushr、lshl、lshr、 lushr</li><li>按位或指令：ior、lor</li><li>按位与指令：iand、land</li><li>按位异或指令：ixor、lxor</li></ul></li><li><p>比较指令：dcmpg、dcmpl、 fcmpg、fcmpl、lcmp</p></li></ul><p>运算模式：</p><ul><li><strong>向最接近数舍入模式</strong>：JVM 在进行浮点数计算时，所有的运算结果都必须舍入到适当的精度，非精确结果必须舍入为可被表示的最接近的精确值，如果有两种可表示形式与该值一样接近，将优先选择最低有效位为零的</li><li><strong>向零舍入模式</strong>：将浮点数转换为整数时，该模式将在目标数值类型中选择一个最接近但是不大于原值的数字作为最精确的舍入结果</li></ul><p>NaN 值：当一个操作产生溢出时，将会使用有符号的无穷大表示，如果某个操作结果没有明确的数学定义，将使用 NaN 值来表示</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">double</span> j <span class="token operator">=</span> i <span class="token operator">/</span> <span class="token number">0.0</span><span class="token punctuation">;</span>System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>j<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//无穷大，NaN: not a number</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>**分析 i++**：从字节码角度分析：a++ 和 ++a 的区别是先执行 iload 还是先执行 iinc</p><pre class="line-numbers language-java"><code class="language-java"> <span class="token number">4</span> iload_1<span class="token comment" spellcheck="true">//存入操作数栈</span> <span class="token number">5</span> iinc <span class="token number">1</span> by <span class="token number">1</span><span class="token comment" spellcheck="true">//自增i++</span> <span class="token number">8</span> istore_3<span class="token comment" spellcheck="true">//把操作数栈没有自增的数据的存入局部变量表</span>    <span class="token number">9</span> iinc <span class="token number">2</span> by <span class="token number">1</span><span class="token comment" spellcheck="true">//++i</span><span class="token number">12</span> iload_2<span class="token comment" spellcheck="true">//加载到操作数栈</span><span class="token number">13</span> istore <span class="token number">4</span><span class="token comment" spellcheck="true">//存入局部变量表，这个存入没有 _ 符号，_只能到3</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Demo</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">int</span> a <span class="token operator">=</span> <span class="token number">10</span><span class="token punctuation">;</span>        <span class="token keyword">int</span> b <span class="token operator">=</span> a<span class="token operator">++</span> <span class="token operator">+</span> <span class="token operator">++</span>a <span class="token operator">+</span> a<span class="token operator">--</span><span class="token punctuation">;</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>a<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//11</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>b<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//34</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>判断结果：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Demo</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>        <span class="token keyword">int</span> x <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>        <span class="token keyword">while</span> <span class="token punctuation">(</span>i <span class="token operator">&lt;</span> <span class="token number">10</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            x <span class="token operator">=</span> x<span class="token operator">++</span><span class="token punctuation">;</span>            i<span class="token operator">++</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>x<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 结果是 0</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="3-类型转换指令"><a href="#3-类型转换指令" class="headerlink" title="3.类型转换指令"></a>3.类型转换指令</h3><blockquote><p>类型转换指令可以将两种不同的数值类型进行相互转换，除了 boolean 之外的七种类型</p></blockquote><p>宽化类型转换：</p><ul><li><p>JVM 支持以下数值的宽化类型转换（widening numeric conversion），小范围类型到大范围类型的安全转换</p><ul><li>从 int 类型到 long、float 或者 double 类型，对应的指令为 i2l、i2f、i2d</li><li>从 long 类型到 float、 double 类型，对应的指令为 l2f、l2d</li><li>从 float 类型到 double 类型，对应的指令为 f2d</li></ul></li><li><p>精度损失问题</p><ul><li>宽化类型转换是不会因为超过目标类型最大值而丢失信息</li><li>从 int 转换到 float 或者 long 类型转换到 double 时，将可能发生精度丢失</li></ul></li><li><p>从 byte、char 和 short 类型到 int 类型的宽化类型转换实际上是不存在的，JVM 把它们当作 int 处理</p></li></ul><p>窄化类型转换：</p><ul><li><p>Java 虚拟机直接支持以下窄化类型转换：</p><ul><li>从 int 类型至 byte、 short 或者 char 类型，对应的指令有 i2b、i2c、i2s</li><li>从 long 类型到 int 类型，对应的指令有 l2i</li><li>从 float 类型到 int 或者 long 类型，对应的指令有:f2i、f2l</li><li>从 double 类型到 int、long 或 float 者类型，对应的指令有 d2i、d2、d2f</li></ul></li><li><p>精度损失问题：</p><ul><li>窄化类型转换可能会导致转换结果具备不同的正负号、不同数量级，转换过程可能会导致数值丢失精度</li><li>将一个浮点值窄化转换为整数类型 T（T 限于 int 或 long 类型之一）时，将遵循以下转换规则：<ul><li>如果浮点值是 NaN，那转换结果就是 int 或 long 类型的 0</li><li>如果浮点值不是无穷大的话，浮点值使用 IEEE 754 的向零舍入模式取整，获得整数值 v，如果 v 在目标类型 T 的表示范围之内，那转换结果就是 v，否则将根据 v 的符号，转换为 T 所能表示的最大或者最小正数</li></ul></li></ul></li></ul><hr><h3 id="4-对象的创建与访问指令"><a href="#4-对象的创建与访问指令" class="headerlink" title="4.对象的创建与访问指令"></a>4.对象的创建与访问指令</h3><ul><li><p><strong>创建类实例指令</strong>：new，接收一个操作数指向常量池的索引，表示要创建的类型，执行完成后将对象的引用压入栈</p></li><li><p><strong>创建数组的指令</strong>：newarray、anewarray、multianewarray</p><ul><li>newarray：创建基本类型数组</li><li>anewarray：创建引用类型数组</li><li>multianewarray：创建多维数组</li></ul><p>arraylength：取数组长度的指令。该指令弹出栈顶的数组元素，获取数组的长度，将长度压入栈。</p></li></ul><ul><li><p><strong>字段访问指令</strong>：对象创建后可以通过对象访问指令获取对象实例或数组实例中的字段或者数组元素</p><ul><li>getstatic：从类中获取静态字段（static字段&#x2F;类变量）</li><li>putstatic： 设置类中静态字段的值</li><li>getfield：获取类实例字段（非static字段&#x2F;实例变量）</li><li>putfield：设置类实例字段的值</li></ul></li><li><p><strong>类型检查指令</strong>：检查类实例或数组类型的指令</p><ul><li><p>checkcast：用于检查类型强制转换是否可以进行，如果可以进行 checkcast 指令不会改变操作数栈，否则它会抛出 ClassCastException 异常</p></li><li><p>instanceof：判断给定对象是否是某一个类的实例，会将判断结果压入操作数栈</p></li></ul></li></ul><h3 id="5-方法调用与返回指令"><a href="#5-方法调用与返回指令" class="headerlink" title="5.方法调用与返回指令"></a>5.方法调用与返回指令</h3><h4 id="方法调用指令"><a href="#方法调用指令" class="headerlink" title="方法调用指令"></a>方法调用指令</h4><p>普通调用指令：</p><ul><li>invokestatic：调用静态方法</li><li>invokespecial：调用私有方法、构造器，和父类的实例方法或构造器，以及所实现接口的默认方法</li><li>invokevirtual：调用所有虚方法（虚方法分派），支持多态</li><li>invokeinterface：调用接口方法</li></ul><p>动态调用指令：</p><ul><li>invokedynamic：调用动态绑定的方法，动态解析出需要调用的方法<ul><li>Java7 为了实现动态类型语言支持而引入了该指令，但是并没有提供直接生成 invokedynamic 指令的方法，需要借助 ASM 这种底层字节码工具来产生 invokedynamic 指令</li><li>Java8 的 lambda 表达式的出现，invokedynamic 指令在 Java 中才有了直接生成方式</li></ul></li></ul><p>指令对比：</p><ul><li>普通调用指令固化在虚拟机内部，方法的调用执行不可干预，根据方法的符号引用链接到具体的目标方法</li><li>动态调用指令支持用户确定方法</li><li>invokestatic 和 invokespecial 指令调用的方法称为非虚方法，虚拟机能够直接识别具体的目标方法</li><li>invokevirtual 和 invokeinterface 指令调用的方法称为虚方法，虚拟机需要在执行过程中根据调用者的动态类型来确定目标方法</li></ul><p>指令说明：</p><ul><li>如果虚拟机能够确定目标方法有且仅有一个，比如说目标方法被标记为 final，那么可以不通过动态绑定，直接确定目标方法</li><li>普通成员方法是由 invokevirtual 调用，属于<strong>动态绑定</strong>，即支持多态</li></ul><h4 id="方法返回指令"><a href="#方法返回指令" class="headerlink" title="方法返回指令"></a>方法返回指令</h4><p>方法调用结束前，需要进行返回。方法返回指令是根据返回值的类型区分的。</p><table><thead><tr><th>方法返回指令</th><th>void</th><th>int</th><th>long</th><th>float</th><th>double</th><th>reference</th></tr></thead><tbody><tr><td><strong>xreturn</strong></td><td>return</td><td>ireturn</td><td>lreturn</td><td>freutrn</td><td>dreturn</td><td>areturn</td></tr></tbody></table><p>ireturn（当返回值是boolean、byte、char、short和int 类型时使用）</p><p>通过ireturn指令，将当前函数操作数栈的顶层元素弹出，并将这个元素压入调用者函数的操作数栈中（因为调用者非常关心函数的返回值），所有在当前函数操作数栈中的其他元素都会被丢弃。</p><p>如果当前返回的是synchronized方法，那么还会执行一个隐含的monitorexit指令，退出临界区。</p><p>最后，会丢弃当前方法的整个帧，恢复调用者的帧，并将控制权转交给调用者。</p><h3 id="6-操作数栈管理指令"><a href="#6-操作数栈管理指令" class="headerlink" title="6.操作数栈管理指令"></a>6.操作数栈管理指令</h3><blockquote><p>JVM 提供的操作数栈管理指令，可以用于直接操作操作数栈的指令</p></blockquote><ul><li><p>pop、pop2：将一个或两个元素从栈顶弹出，并且直接废弃</p></li><li><p>dup、dup2，dup_x1、dup2_x1，dup_x2、dup2_x2：复制栈顶一个或两个数值并重新压入栈顶</p></li><li><p>swap：将栈最顶端的两个 slot 数值位置交换，JVM 没有提供交换两个 64 位数据类型数值的指令</p></li><li><p>nop：一个非常特殊的指令，字节码为 0x00，和汇编语言中的 nop 一样，表示什么都不做，一般可用于调试、占位等</p></li></ul><h3 id="7-比较控制指令"><a href="#7-比较控制指令" class="headerlink" title="7.比较控制指令"></a>7.比较控制指令</h3><p>比较指令：比较栈顶两个元素的大小，并将比较结果入栈</p><ul><li>lcmp：比较两个 long 类型值</li><li>fcmpl：比较两个 float 类型值（当遇到NaN时，返回-1）</li><li>fcmpg：比较两个 float 类型值（当遇到NaN时，返回1）</li><li>dcmpl：比较两个 double 类型值（当遇到NaN时，返回-1）</li><li>dcmpg：比较两个 double 类型值（当遇到NaN时，返回1）</li></ul><p>条件跳转指令：</p><table><thead><tr><th>指令</th><th>说明</th></tr></thead><tbody><tr><td>ifeq</td><td>equals，当栈顶int类型数值等于0时跳转</td></tr><tr><td>ifne</td><td>not equals，当栈顶in类型数值不等于0时跳转</td></tr><tr><td>iflt</td><td>lower than，当栈顶in类型数值小于0时跳转</td></tr><tr><td>ifle</td><td>lower or equals，当栈顶in类型数值小于等于0时跳转</td></tr><tr><td>ifgt</td><td>greater than，当栈顶int类型数组大于0时跳转</td></tr><tr><td>ifge</td><td>greater or equals，当栈顶in类型数值大于等于0时跳转</td></tr><tr><td>ifnull</td><td>为 null 时跳转</td></tr><tr><td>ifnonnull</td><td>不为 null 时跳转</td></tr></tbody></table><p>比较条件跳转指令：</p><table><thead><tr><th>指令</th><th>说明</th></tr></thead><tbody><tr><td>if_icmpeq</td><td>比较栈顶两 int 类型数值大小（下同），当前者等于后者时跳转</td></tr><tr><td>if_icmpne</td><td>当前者不等于后者时跳转</td></tr><tr><td>if_icmplt</td><td>当前者小于后者时跳转</td></tr><tr><td>if_icmple</td><td>当前者小于等于后者时跳转</td></tr><tr><td>if_icmpgt</td><td>当前者大于后者时跳转</td></tr><tr><td>if_icmpge</td><td>当前者大于等于后者时跳转</td></tr><tr><td>if_acmpeq</td><td>当结果相等时跳转</td></tr><tr><td>if_acmpne</td><td>当结果不相等时跳转</td></tr></tbody></table><p>多条件分支跳转指令：</p><ul><li>tableswitch：用于 switch 条件跳转，case 值连续</li><li>lookupswitch：用于 switch 条件跳转，case 值不连续</li></ul><p>无条件跳转指令：</p><ul><li><p>goto：用来进行跳转到指定行号的字节码</p></li><li><p>goto_w：无条件跳转（宽索引）</p></li></ul><h3 id="8-异常处理指令"><a href="#8-异常处理指令" class="headerlink" title="8.异常处理指令"></a>8.异常处理指令</h3><p><strong>抛出异常指令</strong>：athrow 指令，在Java程序中显示抛出异常的操作（throw语句）都是由athrow指令来实现。</p><p><strong>处理异常</strong>：</p><p>JVM 处理异常（catch 语句）不是由字节码指令来实现的，而是<strong>采用异常表来完成</strong>的</p><ul><li><p>代码：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>        <span class="token keyword">try</span> <span class="token punctuation">{</span>            i <span class="token operator">=</span> <span class="token number">10</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Exception</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span>           i <span class="token operator">=</span> <span class="token number">20</span><span class="token punctuation">;</span>       <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>        i <span class="token operator">=</span> <span class="token number">30</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>字节码：</p><pre class="line-numbers language-java"><code class="language-java">    <span class="token number">0</span><span class="token operator">:</span> iconst_0    <span class="token number">1</span><span class="token operator">:</span> istore_1 <span class="token comment" spellcheck="true">// 0 -> i->赋值</span>    <span class="token number">2</span><span class="token operator">:</span> bipush <span class="token number">10</span> <span class="token comment" spellcheck="true">// try 10 放入操作数栈顶</span>    <span class="token number">4</span><span class="token operator">:</span> istore_1 <span class="token comment" spellcheck="true">// 10 -> i 将操作数栈顶数据弹出，存入局部变量表的 slot1</span>    <span class="token number">5</span><span class="token operator">:</span> bipush <span class="token number">30</span> <span class="token comment" spellcheck="true">// 【finally】 </span>    <span class="token number">7</span><span class="token operator">:</span> istore_1 <span class="token comment" spellcheck="true">// 30 -> i </span>    <span class="token number">8</span><span class="token operator">:</span> <span class="token keyword">goto</span> <span class="token number">27</span> <span class="token comment" spellcheck="true">// return </span>    <span class="token number">11</span><span class="token operator">:</span> astore_2 <span class="token comment" spellcheck="true">// catch Exceptin -> e </span>    <span class="token number">12</span><span class="token operator">:</span> bipush <span class="token number">20</span> <span class="token comment" spellcheck="true">// </span>    <span class="token number">14</span><span class="token operator">:</span> istore_1 <span class="token comment" spellcheck="true">// 20 -> i </span>    <span class="token number">15</span><span class="token operator">:</span> bipush <span class="token number">30</span> <span class="token comment" spellcheck="true">// 【finally】 </span>    <span class="token number">17</span><span class="token operator">:</span> istore_1 <span class="token comment" spellcheck="true">// 30 -> i </span>    <span class="token number">18</span><span class="token operator">:</span> <span class="token keyword">goto</span> <span class="token number">27</span> <span class="token comment" spellcheck="true">// return </span>    <span class="token number">21</span><span class="token operator">:</span> astore_3 <span class="token comment" spellcheck="true">// catch any -> slot 3 </span>    <span class="token number">22</span><span class="token operator">:</span> bipush <span class="token number">30</span> <span class="token comment" spellcheck="true">// 【finally】</span>    <span class="token number">24</span><span class="token operator">:</span> istore_1 <span class="token comment" spellcheck="true">// 30 -> i </span>    <span class="token number">25</span><span class="token operator">:</span> aload_3 <span class="token comment" spellcheck="true">// 将局部变量表的slot 3数据弹出，放入操作数栈栈顶</span>    <span class="token number">26</span><span class="token operator">:</span> athrow <span class="token comment" spellcheck="true">// throw 抛出异常</span>    <span class="token number">27</span><span class="token operator">:</span> <span class="token keyword">return</span>Exception table<span class="token operator">:</span>  <span class="token comment" spellcheck="true">// 任何阶段出现任务异常都会执行 finally</span>    from   to target type        <span class="token number">2</span>   <span class="token number">5</span>   <span class="token number">11</span>   Class <span class="token class-name">java</span><span class="token operator">/</span>lang<span class="token operator">/</span>Exception        <span class="token number">2</span>  <span class="token number">5</span>   <span class="token number">21</span>   any <span class="token comment" spellcheck="true">// 剩余的异常类型，比如 Error</span>        <span class="token number">11</span>  <span class="token number">15</span>   <span class="token number">21</span>   any <span class="token comment" spellcheck="true">// 剩余的异常类型，比如 Error</span>LineNumberTable<span class="token operator">:</span> <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>LocalVariableTable<span class="token operator">:</span>    Start Length Slot Name Signature    <span class="token number">12</span>   <span class="token number">3</span> <span class="token number">2</span>  e   Ljava<span class="token operator">/</span>lang<span class="token operator">/</span>Exception<span class="token punctuation">;</span>     <span class="token number">0</span>  <span class="token number">28</span> <span class="token number">0</span>   args <span class="token punctuation">[</span>Ljava<span class="token operator">/</span>lang<span class="token operator">/</span>String<span class="token punctuation">;</span>     <span class="token number">2</span>  <span class="token number">26</span> <span class="token number">1</span>  i   I<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>如果一个方法定义了一个try-catch 或者try-finally的异常处理，就会创建一个 <strong>Exception table</strong> 的结构，异常表保存了每个异常处理信息</p></li></ul><pre class="line-numbers language-java"><code class="language-java">Exception table<span class="token operator">:</span>      from   to target type        <span class="token number">2</span>   <span class="token number">5</span>  <span class="token number">11</span>   Class <span class="token class-name">java</span><span class="token operator">/</span>lang<span class="token operator">/</span>Exception        <span class="token number">2</span>  <span class="token number">5</span>  <span class="token number">21</span>   any <span class="token comment" spellcheck="true">// 剩余的异常类型，比如 Error</span>     <span class="token number">11</span>   <span class="token number">15</span>  <span class="token number">21</span>   any <span class="token comment" spellcheck="true">// 剩余的异常类型，比如 Error</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li><strong>[from, to) 是前闭后开的检测范围</strong>，一旦这个范围内的字节码执行出现异常，则通过 type 匹配异常类型，如果一致，进入 target 所指示行号</li></ul><ul><li>11 行的字节码指令 astore_2 是将异常对象引用存入局部变量表的 slot 2 位置，如果有多个catch语句，因为异常出现时，只能进入 Exception table 中一个分支，所以局部变量表 slot 2 位置被共用</li><li>字节码中 finally 中的代码被<strong>复制了 3 份</strong>，分别放入 try 流程，catch 流程以及 catch 剩余的异常类型流程，因此finally中的代码一定会执行</li></ul><h3 id="9-同步控制指令"><a href="#9-同步控制指令" class="headerlink" title="9.同步控制指令"></a>9.同步控制指令</h3><blockquote><p>Java虚拟机支持两种同步结构：方法级的同步和方法内部一段指令序列的同步，这两种同步都是使用monitor来支持的</p></blockquote><p><strong>方法级的同步</strong>：是隐式的，无须通过字节码指令来控制，它实现在方法调用和返回操作之中，虚拟机可以从方法常量池的方法表结构中的 ACC_SYNCHRONIZED 访问标志得知一个方法是否声明为同步方法</p><p><strong>方法内指定指令序列的同步</strong>：有 monitorenter 和 monitorexit 两条指令来支持 synchronized 关键字的语义</p><ul><li>montiorenter：进入并获取对象监视器，即为栈顶对象加锁</li><li>monitorexit：释放并退出对象监视器，即为栈顶对象解锁</li></ul><img src="https://img.jwt1399.top/img/202212231517239.png" style="zoom: 33%;" /><h3 id="🌟图解字节码运行"><a href="#🌟图解字节码运行" class="headerlink" title="🌟图解字节码运行"></a>🌟图解字节码运行</h3><p>1）原始java 代码</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">/*    演示 字节码指令 和 操作数栈、常量池的关系 */</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Demo</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">int</span> a <span class="token operator">=</span> <span class="token number">10</span><span class="token punctuation">;</span>        <span class="token keyword">int</span> b <span class="token operator">=</span> Short<span class="token punctuation">.</span>MAX_VALUE <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//32767 + 1</span>        <span class="token keyword">int</span> c <span class="token operator">=</span> a <span class="token operator">+</span> b<span class="token punctuation">;</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>c<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>2）编译后的核心字节码文件</p><pre class="line-numbers language-java"><code class="language-java">  MD5 checksum cc8fc12b6e178b8f28e787497e993363  Compiled from <span class="token string">"Demo.java"</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">com<span class="token punctuation">.</span>jvm<span class="token punctuation">.</span>test<span class="token punctuation">.</span>Demo</span>  minor version<span class="token operator">:</span> <span class="token number">0</span>  major version<span class="token operator">:</span> <span class="token number">52</span>  flags<span class="token operator">:</span> ACC_PUBLIC<span class="token punctuation">,</span> ACC_SUPERConstant pool<span class="token operator">:</span>   #<span class="token number">1</span> <span class="token operator">=</span> Methodref          #<span class="token number">7</span><span class="token punctuation">.</span>#<span class="token number">25</span>         <span class="token comment" spellcheck="true">// java/lang/Object."&lt;init>":()V</span>   #<span class="token number">2</span> <span class="token operator">=</span> Class              #<span class="token number">26</span>            <span class="token comment" spellcheck="true">// java/lang/Short</span>   #<span class="token number">3</span> <span class="token operator">=</span> Integer            <span class="token number">32768</span>   #<span class="token number">4</span> <span class="token operator">=</span> Fieldref           #<span class="token number">27</span><span class="token punctuation">.</span>#<span class="token number">28</span>        <span class="token comment" spellcheck="true">// java/lang/System.out:Ljava/io/PrintStream;</span>   #<span class="token number">5</span> <span class="token operator">=</span> Methodref          #<span class="token number">29</span><span class="token punctuation">.</span>#<span class="token number">30</span>        <span class="token comment" spellcheck="true">// java/io/PrintStream.println:(I)V</span>   #<span class="token number">6</span> <span class="token operator">=</span> Class              #<span class="token number">31</span>            <span class="token comment" spellcheck="true">// com/jvm/test/Demo</span>   #<span class="token number">7</span> <span class="token operator">=</span> Class              #<span class="token number">32</span>            <span class="token comment" spellcheck="true">// java/lang/Object</span>   #<span class="token number">8</span> <span class="token operator">=</span> Utf8               <span class="token operator">&lt;</span>init<span class="token operator">></span>   #<span class="token number">9</span> <span class="token operator">=</span> <span class="token function">Utf8</span>               <span class="token punctuation">(</span><span class="token punctuation">)</span>V  #<span class="token number">10</span> <span class="token operator">=</span> Utf8               Code  #<span class="token number">11</span> <span class="token operator">=</span> Utf8               LineNumberTable  #<span class="token number">12</span> <span class="token operator">=</span> Utf8               LocalVariableTable  #<span class="token number">13</span> <span class="token operator">=</span> Utf8               <span class="token keyword">this</span>  #<span class="token number">14</span> <span class="token operator">=</span> Utf8               Lcom<span class="token operator">/</span>jvm<span class="token operator">/</span>test<span class="token operator">/</span>Demo<span class="token punctuation">;</span>  #<span class="token number">15</span> <span class="token operator">=</span> Utf8               main  #<span class="token number">16</span> <span class="token operator">=</span> <span class="token function">Utf8</span>               <span class="token punctuation">(</span><span class="token punctuation">[</span>Ljava<span class="token operator">/</span>lang<span class="token operator">/</span>String<span class="token punctuation">;</span><span class="token punctuation">)</span>V  #<span class="token number">17</span> <span class="token operator">=</span> Utf8               args  #<span class="token number">18</span> <span class="token operator">=</span> Utf8               <span class="token punctuation">[</span>Ljava<span class="token operator">/</span>lang<span class="token operator">/</span>String<span class="token punctuation">;</span>  #<span class="token number">19</span> <span class="token operator">=</span> Utf8               a  #<span class="token number">20</span> <span class="token operator">=</span> Utf8               I  #<span class="token number">21</span> <span class="token operator">=</span> Utf8               b  #<span class="token number">22</span> <span class="token operator">=</span> Utf8               c  #<span class="token number">23</span> <span class="token operator">=</span> Utf8               SourceFile  #<span class="token number">24</span> <span class="token operator">=</span> Utf8               Demo<span class="token punctuation">.</span>java  #<span class="token number">25</span> <span class="token operator">=</span> NameAndType        #<span class="token number">8</span><span class="token operator">:</span>#<span class="token number">9</span>          <span class="token comment" spellcheck="true">// "&lt;init>":()V</span>  #<span class="token number">26</span> <span class="token operator">=</span> Utf8               java<span class="token operator">/</span>lang<span class="token operator">/</span>Short  #<span class="token number">27</span> <span class="token operator">=</span> Class              #<span class="token number">33</span>            <span class="token comment" spellcheck="true">// java/lang/System</span>  #<span class="token number">28</span> <span class="token operator">=</span> NameAndType        #<span class="token number">34</span><span class="token operator">:</span>#<span class="token number">35</span>        <span class="token comment" spellcheck="true">// out:Ljava/io/PrintStream;</span>  #<span class="token number">29</span> <span class="token operator">=</span> Class              #<span class="token number">36</span>            <span class="token comment" spellcheck="true">// java/io/PrintStream</span>  #<span class="token number">30</span> <span class="token operator">=</span> NameAndType        #<span class="token number">37</span><span class="token operator">:</span>#<span class="token number">38</span>        <span class="token comment" spellcheck="true">// println:(I)V</span>  #<span class="token number">31</span> <span class="token operator">=</span> Utf8               com<span class="token operator">/</span>jvm<span class="token operator">/</span>test<span class="token operator">/</span>Demo  #<span class="token number">32</span> <span class="token operator">=</span> Utf8               java<span class="token operator">/</span>lang<span class="token operator">/</span>Object  #<span class="token number">33</span> <span class="token operator">=</span> Utf8               java<span class="token operator">/</span>lang<span class="token operator">/</span>System  #<span class="token number">34</span> <span class="token operator">=</span> Utf8               out  #<span class="token number">35</span> <span class="token operator">=</span> Utf8               Ljava<span class="token operator">/</span>io<span class="token operator">/</span>PrintStream<span class="token punctuation">;</span>  #<span class="token number">36</span> <span class="token operator">=</span> Utf8               java<span class="token operator">/</span>io<span class="token operator">/</span>PrintStream  #<span class="token number">37</span> <span class="token operator">=</span> Utf8               println  #<span class="token number">38</span> <span class="token operator">=</span> <span class="token function">Utf8</span>               <span class="token punctuation">(</span>I<span class="token punctuation">)</span>V<span class="token punctuation">{</span>  <span class="token keyword">public</span> com<span class="token punctuation">.</span>jvm<span class="token punctuation">.</span>test<span class="token punctuation">.</span><span class="token function">Demo</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    descriptor<span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span>V    flags<span class="token operator">:</span> ACC_PUBLIC    Code<span class="token operator">:</span>      stack<span class="token operator">=</span><span class="token number">1</span><span class="token punctuation">,</span> locals<span class="token operator">=</span><span class="token number">1</span><span class="token punctuation">,</span> args_size<span class="token operator">=</span><span class="token number">1</span>         <span class="token number">0</span><span class="token operator">:</span> aload_0         <span class="token number">1</span><span class="token operator">:</span> invokespecial #<span class="token number">1</span>                  <span class="token comment" spellcheck="true">// Method java/lang/Object."&lt;init>":()V</span>         <span class="token number">4</span><span class="token operator">:</span> <span class="token keyword">return</span>      LineNumberTable<span class="token operator">:</span>        line <span class="token number">15</span><span class="token operator">:</span> <span class="token number">0</span>      LocalVariableTable<span class="token operator">:</span>        Start  Length  Slot  Name   Signature            <span class="token number">0</span>       <span class="token number">5</span>     <span class="token number">0</span>  <span class="token keyword">this</span>   Lcom<span class="token operator">/</span>jvm<span class="token operator">/</span>test<span class="token operator">/</span>Demo<span class="token punctuation">;</span>   <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>java<span class="token punctuation">.</span>lang<span class="token punctuation">.</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    descriptor<span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">[</span>Ljava<span class="token operator">/</span>lang<span class="token operator">/</span>String<span class="token punctuation">;</span><span class="token punctuation">)</span>V    flags<span class="token operator">:</span> ACC_PUBLIC<span class="token punctuation">,</span> ACC_STATIC    Code<span class="token operator">:</span>      stack<span class="token operator">=</span><span class="token number">2</span><span class="token punctuation">,</span> locals<span class="token operator">=</span><span class="token number">4</span><span class="token punctuation">,</span> args_size<span class="token operator">=</span><span class="token number">1</span>         <span class="token number">0</span><span class="token operator">:</span> bipush        <span class="token number">10</span>         <span class="token number">2</span><span class="token operator">:</span> istore_1         <span class="token number">3</span><span class="token operator">:</span> ldc           #<span class="token number">3</span>                  <span class="token comment" spellcheck="true">// int 32768</span>         <span class="token number">5</span><span class="token operator">:</span> istore_2         <span class="token number">6</span><span class="token operator">:</span> iload_1         <span class="token number">7</span><span class="token operator">:</span> iload_2         <span class="token number">8</span><span class="token operator">:</span> iadd         <span class="token number">9</span><span class="token operator">:</span> istore_3        <span class="token number">10</span><span class="token operator">:</span> getstatic     #<span class="token number">4</span>        <span class="token comment" spellcheck="true">// Field java/lang/System.out:Ljava/io/PrintStream;</span>        <span class="token number">13</span><span class="token operator">:</span> iload_3        <span class="token number">14</span><span class="token operator">:</span> invokevirtual #<span class="token number">5</span>        <span class="token comment" spellcheck="true">// Method java/io/PrintStream.println:(I)V</span>        <span class="token number">17</span><span class="token operator">:</span> <span class="token keyword">return</span>      LineNumberTable<span class="token operator">:</span>        line <span class="token number">17</span><span class="token operator">:</span> <span class="token number">0</span>        line <span class="token number">18</span><span class="token operator">:</span> <span class="token number">3</span>        line <span class="token number">19</span><span class="token operator">:</span> <span class="token number">6</span>        line <span class="token number">20</span><span class="token operator">:</span> <span class="token number">10</span>        line <span class="token number">21</span><span class="token operator">:</span> <span class="token number">17</span>      LocalVariableTable<span class="token operator">:</span>        Start  Length  Slot  Name   Signature            <span class="token number">0</span>      <span class="token number">18</span>     <span class="token number">0</span>  args   <span class="token punctuation">[</span>Ljava<span class="token operator">/</span>lang<span class="token operator">/</span>String<span class="token punctuation">;</span>            <span class="token number">3</span>      <span class="token number">15</span>     <span class="token number">1</span>     a   I            <span class="token number">6</span>      <span class="token number">12</span>     <span class="token number">2</span>     b   I           <span class="token number">10</span>       <span class="token number">8</span>     <span class="token number">3</span>     c   I<span class="token punctuation">}</span>SourceFile<span class="token operator">:</span> <span class="token string">"Demo.java"</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>3）常量池载入运行时常量池</p><p><img src="https://img.jwt1399.top/img/202212231525849.png"></p><p>4）方法字节码载入方法区</p><p><img src="https://img.jwt1399.top/img/202212231525099.png"></p><p>5）main线程开始运行，分配栈帧内存（stack&#x3D;2,  locals&#x3D;4）</p><p><img src="https://img.jwt1399.top/img/202212231525266.png">  </p><p>6）执行引擎开始执行字节码</p><p>bipush 10</p><ul><li>将一个byte压入操作数栈（其长度会补齐4个字节），类似的指令还有</li><li>sipush将一个short压入操作数栈（其长度会补齐4个字节）</li><li>idc将一个int压入操作数栈</li><li>idc2_w将一个long压入操作数栈（分两次压入，因为long是8个字节）</li><li>这里小的数字都是和字节码指令存在一起，超过short范围的数字存入常量池</li></ul><p><img src="https://img.jwt1399.top/img/202212231526930.png"></p><p>istore_1</p><ul><li>将操作数栈顶数据弹出，存入局部变量表的槽位slot 1中，下图的args&#x2F;a&#x2F;b&#x2F;c就是对应的槽位</li></ul><p><img src="https://img.jwt1399.top/img/202212231526080.png"></p><p><img src="https://img.jwt1399.top/img/202212231526737.png"></p><p>idc  #3</p><ul><li>idc 从常量池加载 #3 数据到操作数栈</li><li><strong>注意：</strong> Short.MAX_VALUE是32767，所以32768 &#x3D; Short.MAX_VALUE + 1 超过了Short.MAX_VALUE的值，所以编译器将其放在运行时常量池中。实际是在编译期间计算好的，是一种优化，叫常量折叠优化</li></ul><p><img src="https://img.jwt1399.top/img/202212231526161.png"></p><p>istore_2</p><ul><li>将操作数栈顶数据弹出，存入局部变量表的2号槽位中</li></ul><p><img src="https://img.jwt1399.top/img/202212231526604.png"></p><p><img src="https://img.jwt1399.top/img/202212231526004.png"></p><p>iload_1</p><p>再接下来，需要执行int c &#x3D; a + b；执行引擎不能直接在局部变量表进行a+b操作，需要先将a、b进行读取，然后放入操作数栈中才能进行计算分析</p><ul><li>把局部变量从1号槽位加载数据到操作数栈中</li></ul><p><img src="https://img.jwt1399.top/img/202212231526636.png"></p><p>iload 2</p><ul><li>再把局部变量从2号槽位加载数据到操作数栈中</li></ul><p><img src="https://img.jwt1399.top/img/202212231526141.png"></p><p>iadd</p><ul><li>iadd会弹出操作数栈中的2个变量，并进行求和得到32778，最后将结果32778写回到操作数栈中</li></ul><p><img src="https://img.jwt1399.top/img/202212231526224.png"></p><p><img src="https://img.jwt1399.top/img/202212231527135.png"><br>istore_3</p><ul><li>将操作数栈的数弹出，存入局部变量表3号槽位。到目前为止，局部变量a、b、c都有值了</li></ul><p><img src="https://img.jwt1399.top/img/202212231527590.png"></p><p><img src="https://img.jwt1399.top/img/202212231527063.png"></p><p>getstatic #4</p><ul><li>去常量池找到成员变量引用，去堆中找到System.out对象。getstatic 会把对象引用放入操作数栈中</li></ul><p><img src="https://img.jwt1399.top/img/202212231527694.png"></p><p><img src="https://img.jwt1399.top/img/202212231527400.png"></p><p>iload_3</p><ul><li>将局部变量表3号槽位的值加载到操作数栈中</li></ul><p><img src="https://img.jwt1399.top/img/202212231527091.png"></p><p><img src="https://img.jwt1399.top/img/202212231527091.png"></p><p>invokevirtual #5</p><ul><li>找到常量池 #5项</li><li>定位到方法区 java&#x2F;io&#x2F;PrintStream.println: (I)V方法</li><li>生成新的栈帧（分配locals、stack等）</li><li>传递传数，执行新栈帧中的字节码</li></ul><p><img src="https://img.jwt1399.top/img/202212231528002.png"></p><ul><li>执行完毕，弹出栈帧</li><li>清除main操作数栈内容</li></ul><p><img src="https://img.jwt1399.top/img/202212231528262.png"></p><p>return</p><ul><li>完成main方法调用，弹出main栈帧</li><li>程序结束</li></ul><p>参考：<a href="https://blog.csdn.net/weixin_32265569/article/details/107904061">Java字节码的一段旅行经历——提升硬实力1</a></p><h3 id="🌟a-字节码分析"><a href="#🌟a-字节码分析" class="headerlink" title="🌟a++ 字节码分析"></a>🌟a++ 字节码分析</h3><p>在日常的项目开发中，经常遇到a++、++a、a–之类，下面我们开始从字节码的视角来分析a++。</p><p>java代码如下：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">/* 从字节码角度分析  a++ 相关题目 */</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Demo</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">int</span> a <span class="token operator">=</span> <span class="token number">10</span><span class="token punctuation">;</span>        <span class="token keyword">int</span> b <span class="token operator">=</span> a<span class="token operator">++</span> <span class="token operator">+</span> <span class="token operator">++</span>a <span class="token operator">+</span> a<span class="token operator">--</span><span class="token punctuation">;</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>a<span class="token punctuation">)</span><span class="token punctuation">;</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>b<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>使用javap -v xxx.class 来查看类文件全部指令信息，如下：</p><pre class="line-numbers language-java"><code class="language-java">  <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>java<span class="token punctuation">.</span>lang<span class="token punctuation">.</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    descriptor<span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">[</span>Ljava<span class="token operator">/</span>lang<span class="token operator">/</span>String<span class="token punctuation">;</span><span class="token punctuation">)</span>V    flags<span class="token operator">:</span> ACC_PUBLIC<span class="token punctuation">,</span> ACC_STATIC    Code<span class="token operator">:</span>      stack<span class="token operator">=</span><span class="token number">2</span><span class="token punctuation">,</span> locals<span class="token operator">=</span><span class="token number">3</span><span class="token punctuation">,</span> args_size<span class="token operator">=</span><span class="token number">1</span>         <span class="token number">0</span><span class="token operator">:</span> bipush        <span class="token number">10</span>         <span class="token number">2</span><span class="token operator">:</span> istore_1         <span class="token number">3</span><span class="token operator">:</span> iload_1         <span class="token number">4</span><span class="token operator">:</span> iinc          <span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">1</span>         <span class="token number">7</span><span class="token operator">:</span> iinc          <span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">1</span>        <span class="token number">10</span><span class="token operator">:</span> iload_1        <span class="token number">11</span><span class="token operator">:</span> iadd        <span class="token number">12</span><span class="token operator">:</span> iload_1        <span class="token number">13</span><span class="token operator">:</span> iinc          <span class="token number">1</span><span class="token punctuation">,</span> <span class="token operator">-</span><span class="token number">1</span>        <span class="token number">16</span><span class="token operator">:</span> iadd        <span class="token number">17</span><span class="token operator">:</span> istore_2        <span class="token number">18</span><span class="token operator">:</span> getstatic     #<span class="token number">2</span>          <span class="token comment" spellcheck="true">// Field java/lang/System.out:Ljava/io/PrintStream;</span>        <span class="token number">21</span><span class="token operator">:</span> iload_1        <span class="token number">22</span><span class="token operator">:</span> invokevirtual #<span class="token number">3</span>          <span class="token comment" spellcheck="true">// Method java/io/PrintStream.println:(I)V</span>        <span class="token number">25</span><span class="token operator">:</span> getstatic     #<span class="token number">2</span>          <span class="token comment" spellcheck="true">// Field java/lang/System.out:Ljava/io/PrintStream;</span>        <span class="token number">28</span><span class="token operator">:</span> iload_2        <span class="token number">29</span><span class="token operator">:</span> invokevirtual #<span class="token number">3</span>          <span class="token comment" spellcheck="true">// Method java/io/PrintStream.println:(I)V</span>        <span class="token number">32</span><span class="token operator">:</span> <span class="token keyword">return</span>      LineNumberTable<span class="token operator">:</span>        line <span class="token number">17</span><span class="token operator">:</span> <span class="token number">0</span>        line <span class="token number">18</span><span class="token operator">:</span> <span class="token number">3</span>        line <span class="token number">19</span><span class="token operator">:</span> <span class="token number">18</span>        line <span class="token number">20</span><span class="token operator">:</span> <span class="token number">25</span>        line <span class="token number">21</span><span class="token operator">:</span> <span class="token number">32</span>      LocalVariableTable<span class="token operator">:</span>        Start  Length  Slot  Name   Signature            <span class="token number">0</span>      <span class="token number">33</span>     <span class="token number">0</span>  args   <span class="token punctuation">[</span>Ljava<span class="token operator">/</span>lang<span class="token operator">/</span>String<span class="token punctuation">;</span>            <span class="token number">3</span>      <span class="token number">30</span>     <span class="token number">1</span>     a   I           <span class="token number">18</span>      <span class="token number">15</span>     <span class="token number">2</span>     b   I<span class="token punctuation">}</span>SourceFile<span class="token operator">:</span> <span class="token string">"Demo.java"</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>分析：</p><ul><li>iinc指令是直接在局部变量槽位slot上进行运算</li><li>a++ 和 ++a 的区别是先执行 iload 还是先执行 iinc<ul><li>a++是先加载iload，再自增iinc</li><li>++a是先自增iinc，再加载iload</li></ul></li></ul><p>下面我们通过字节码来剖析如下两行代码在内存当中整个执行过程</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">int</span> a <span class="token operator">=</span> <span class="token number">10</span><span class="token punctuation">;</span><span class="token keyword">int</span> b <span class="token operator">=</span> a<span class="token operator">++</span> <span class="token operator">+</span> <span class="token operator">++</span>a <span class="token operator">+</span> a<span class="token operator">--</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>下图是先将10通过bipush 放入操作数栈中</p><p><img src="https://img.jwt1399.top/img/202212231530556.png"></p><p>接着将10从操作数栈上弹出存入局部变量表1号槽位，相当于代码 int a &#x3D; 10 执行完成</p><p><img src="https://img.jwt1399.top/img/202212231530112.png"></p><p>接着执行：int b &#x3D; a++ + ++a + a–; 因为有从左往右的执行顺序，所以先执行a++，先将a的值加载到操作数栈中；通过iload_1加载1号槽位的数据到操作数栈中</p><p><img src="https://img.jwt1399.top/img/202212231530986.png"></p><p>接着执行a++自增1操作，这个操作是在局部变量表中完成的。相当于完成了a++执行</p><p><img src="https://img.jwt1399.top/img/202212231530544.png"></p><p>再接着执行++a自增1操作，这个操作也是在局部变量表中完成的</p><p><img src="https://img.jwt1399.top/img/202212231530149.png"></p><p>接着从局部变量表1号槽位加载数据到操作数栈中，即12入栈，完成发a++  、++a 各自的执行了</p><p><img src="https://img.jwt1399.top/img/202212231530866.png"></p><p>然后，iadd是将操作数栈中弹出（出栈）两个数12、10进行求和操作，得到22，最后将累加的结果22存入栈中。即完成了a++  + ++a的执行</p><p><img src="https://img.jwt1399.top/img/202212231530345.png"></p><p>接着，需要执行a–，先将局部变量表槽位1的数据12加载到操作数栈中</p><p><img src="https://img.jwt1399.top/img/202212231531507.png"></p><p>然后，将局部变量表槽位1的数据自减1</p><p><img src="https://img.jwt1399.top/img/202212231531205.png"></p><p>接着，执行iadd操作，将操作数栈12、22弹出栈后，进行求和操作得到34，再将34结果压入栈</p><p><img src="https://img.jwt1399.top/img/202212231531053.png"></p><p>最后，执行istore_2操作，将操作数栈弹出数据34，并压入局部变量表2号槽位中</p><p><img src="https://img.jwt1399.top/img/202212231531053.png"></p><p><img src="https://img.jwt1399.top/img/202212231533680.png"></p><p>参考：<a href="https://blog.csdn.net/weixin_32265569/article/details/107970980">Java字节码角度分析a++ ——提升硬实力2</a></p><h2 id="❷编译器"><a href="#❷编译器" class="headerlink" title="❷编译器"></a>❷编译器</h2><ul><li><strong>前端编译器</strong>：把 <code>*.java</code> 文件转变成 <code>.class</code> 文件的过程；如 JDK 的 Javac，Eclipse JDT 中的增量式编译器。</li><li><strong>提前编译器</strong>：直接把程序编译成目标机器指令集相关的二进制代码的过程。如 JDK 的 jaotc，GUN Compiler for the Java（GCJ），Excelsior JET 。</li><li><strong>即时编译器</strong>：常称为 JIT 编译器（Just In Time Complier），在运行期把字节码转变成本地机器码的过程；如 HotSpot 虚拟机中的 C1、C2 编译器，Graal 编译器。<ul><li>**客户端编译器 (Client Complier)**：简称 C1；</li><li>**服务端编译器 (Servier Complier)**：简称 C2；</li><li><strong>Graal 编译器</strong>：在 JDK 10 时才出现，长期目标是替代 C2。</li></ul></li></ul><p>C1 编译器会对字节码进行简单可靠的优化，耗时短，以达到更快的编译速度。</p><p>C1 编译器的优化方法：</p><ul><li><p>方法内联：<strong>将调用的函数代码编译到调用点处</strong>，可以减少栈帧的生成，减少参数传递以及跳转过程</p></li><li><p>冗余消除：根据运行时状况进行代码折叠或削除</p></li><li><p>内联缓存：是一种加快动态绑定的优化技术（方法调用部分详解）</p></li></ul><p>C2 编译器进行耗时较长的优化以及激进优化，优化的代码执行效率更高，当激进优化的假设不成立时，再退回使用 C1 编译，这也是使用分层编译的原因</p><p>C2 的优化主要是在全局层面，<strong>逃逸分析</strong>是优化的基础，如果不存在逃逸行为，则可进行如下优化：</p><ul><li>**栈上分配 (Stack Allocations)**：如果一个对象不会逃逸到线程外，那么将会在栈上分配内存来创建这个对象，而不是 Java 堆上，此时对象所占用的内存空间就会随着栈帧的出栈而销毁，从而可以减轻垃圾回收的压力。</li><li>**标量替换 (Scalar Replacement)**：如果一个数据已经无法再分解成为更小的数据类型，那么这些数据就称为标量（如 int、long 等数值类型及 reference 类型等）；反之，如果一个数据可以继续分解，那它就被称为聚合量（如对象）。如果一个对象不会逃逸外方法外，那么就可以将其改为直接创建若干个被这个方法使用的成员变量来替代，从而减少内存占用。</li><li><strong>同步消除 (Synchronization Elimination)<strong>：线程同步本身比较耗时，如果确定一个对象不会逃逸出线程，不被其它线程访问到，那对象的读写就不会存在竞争，则可以消除对该对象的</strong>同步锁</strong>，通过 &#96;</li></ul><h2 id="❸执行引擎-解释-JIT"><a href="#❸执行引擎-解释-JIT" class="headerlink" title="❸执行引擎(解释+JIT)"></a>❸执行引擎(解释+JIT)</h2><p>Java 是<strong>半编译半解释型语言</strong>，将解释执行与编译执行二者结合起来进行：</p><ul><li>解释器：根据预定义的规范对字节码采用逐行解释的方式执行，即将每条字节码指令翻译为对应平台的本地机器指令执行</li><li>即时编译器（JIT : Just In Time Compiler）：虚拟机在运行期把字节码编译成本地机器码后执行，并存入 Code Cache，下次遇到相同的代码直接执行，效率高</li></ul><ul><li>编译器（Compiler）和解释器（Interpreter）的工作都是将程序员的源代码翻译成可执行的机器代码，要么一次性翻译（编译器），要么逐行解释并运行（解释器）。</li></ul><p>HostSpot  的默认执行方式：</p><ul><li>当程序启动后，解释器可以马上发挥作用立即执行，让程序快速启动，省去编译器编译的时间（解释器存在的<strong>必要性</strong>）</li><li>随着程序运行时间的推移，如果虚拟机<u>发现某个方法或代码块的运行特别频繁</u>（<strong>热点探测功能</strong>），就会<u>使用即时编译器</u>将其编译为本地机器码，并使用各种手段进行优化，从而提高执行效率</li></ul><img src="https://img.jwt1399.top/img/202212261031834.png" style="zoom: 67%;" /><p>HotSpot  可以通过 VM 参数设置程序执行方式：</p><ul><li>-Xint：完全采用解释器模式执行程序</li><li>-Xcomp：完全采用即时编译器模式执行程序。如果即时编译出现问题，解释器会介入执行</li><li>-Xmixed：采用解释器 + 即时编译器的混合模式共同执行程序</li></ul><h2 id="❹分层编译"><a href="#❹分层编译" class="headerlink" title="❹分层编译"></a>❹分层编译</h2><blockquote><p>在分层编译的工作模式出现前，采用客户端编译器还是服务端编译器完全取决于虚拟机是运行在客户端模式还是服务端模式下，可以在启动时通过 <code>-client</code> 或 <code>-server</code> 参数进行指定，也可以让虚拟机根据自身版本和宿主机性能来自主选择。</p></blockquote><p>要编译出优化程度越高的代码通常都需要越长的编译时间，为了在程序启动速度与运行效率之间达到最佳平衡，HotSpot 虚拟机在编译子系统中加入了分层编译（Tiered Compilation），JVM 将执行状态分成了 5 个层次：</p><ul><li><strong>第 0 层</strong>：纯解释器执行，并且解释器不开启性能监控功能；</li><li><strong>第 1 层</strong>：使用C1即时编译器编译执行，进行简单可靠的稳定优化，不开启性能监控功能；</li><li><strong>第 2 层</strong>：使用C1即时编译器编译执行，仅开启方法及回边次数统计等有限的性能监控；</li><li><strong>第 3 层</strong>：使用C1即时编译器编译执行，开启全部性能监控；</li><li><strong>第 4 层</strong>：使用C2即时编译器编译执行，并且会根据性能监控信息进行一些不可靠的激进优化。</li></ul><p>C1 编译器会对字节码进行简单可靠的优化，耗时短，以达到更快的编译速度</p><p>C2 编译器进行耗时较长的优化以及激进优化，优化的代码执行效率更高</p><p>实施分层编译后，解释器、C1编译器和C2编译器就会同时工作，可以用C1编译器获取更高的编译速度、用C2编译器来获取更好的编译质量。</p><h2 id="❺热点探测"><a href="#❺热点探测" class="headerlink" title="❺热点探测"></a>❺热点探测</h2><p>即时编译器编译的目标是 “热点代码”，它主要分为以下两类：</p><ul><li>被多次调用的方法。</li><li>被多次执行循环体。这里指的是一个方法只被少量调用过，但方法体内部存在循环次数较多的循环体，此时也认为是热点代码。但编译器编译的仍然是循环体所在的方法，而不会单独编译循环体。</li></ul><p>判断某段代码是否是热点代码的行为称为 “热点探测” （Hot Spot Code Detection），主流的热点探测方法有以下两种：</p><ul><li>**基于采样的热点探测 (Sample Based Hot Spot Code Detection)**：采用这种方法的虚拟机会周期性地检查各个线程的调用栈顶，如果发现某个（或某些）方法经常出现在栈顶，那么就认为它是 “热点方法”。</li><li>**基于计数的热点探测 (Counter Based Hot Spot Code Detection)**：采用这种方法的虚拟机会为每个方法（甚至是代码块）建立计数器，统计方法的执行次数，如果执行次数超过一定的阈值就认为它是 “热点方法”。</li></ul><p>HotSpot VM 采用的热点探测方式是基于计数器的热点探测，为每一个方法都建立 2 个不同类型的计数器：方法调用计数器（Invocation Counter）和回边计数器（BackEdge Counter）</p><ul><li><p>方法调用计数器：用于统计方法被调用的次数，默认阈值在 Client 模式 下是 1500 次，在 Server 模式下是 10000 次（需要进行激进的优化），超过这个阈值，就会触发 JIT 编译，阈值可以通过虚拟机参数 <code>-XX:CompileThreshold</code> 设置</p><p>工作流程：当一个方法被调用时， 会先检查该方法是否存在被 JIT 编译过的版本，存在则使用编译后的本地代码来执行；如果不存在则将此方法的调用计数器值加 1，然后判断方法调用计数器与回边计数器值之和是否超过方法调用计数器的阈值，如果超过阈值会向即时编译器<strong>提交一个该方法的代码编译请求</strong></p></li><li><p>回边计数器：统计一个方法中循环体代码执行的次数，在字节码中控制流向后跳转的指令称为回边</p><p>如果一个方法中的循环体需要执行多次，可以优化为为栈上替换，简称 OSR (On StackReplacement) 编译，<strong>OSR 替换循环代码体的入口，C1、C2 替换的是方法调用的入口</strong>，OSR 编译后会出现方法的整段代码被编译了，但是只有循环体部分才执行编译后的机器码，其他部分仍是解释执行</p></li></ul><h1 id="⑥代码优化"><a href="#⑥代码优化" class="headerlink" title="⑥代码优化"></a>⑥代码优化</h1><h2 id="❶语法糖"><a href="#❶语法糖" class="headerlink" title="❶语法糖"></a>❶语法糖</h2><p>语法糖：指 Java 编译器把 *.java 源码编译为 *.class 字节码的过程中，自动生成和转换的一些代码，主要是为了减轻程序员的负担</p><h3 id="构造器"><a href="#构造器" class="headerlink" title="构造器"></a>构造器</h3><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Candy</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Candy</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 这个无参构造是编译器帮助我们加上的</span>    <span class="token keyword">public</span> <span class="token function">Candy</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">super</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 即调用父类 Object 的无参构造方法，即调用 java/lang/Object."&lt;init>":()V</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="拆装箱"><a href="#拆装箱" class="headerlink" title="拆装箱"></a>拆装箱</h3><p>这段代码在 JDK 5 之前是无法编译通过的</p><pre class="line-numbers language-java"><code class="language-java">Integer x <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span><span class="token keyword">int</span> y <span class="token operator">=</span> x<span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>必须写成如下代码：</p><pre class="line-numbers language-java"><code class="language-java">Integer x <span class="token operator">=</span> Integer<span class="token punctuation">.</span><span class="token function">valueOf</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//装箱</span><span class="token keyword">int</span> y <span class="token operator">=</span> x<span class="token punctuation">.</span><span class="token function">intValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//拆箱</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>JDK5 以后编译阶段自动转换成上述片段</p><h3 id="泛型擦除"><a href="#泛型擦除" class="headerlink" title="泛型擦除"></a>泛型擦除</h3><p>泛型也是在 JDK 5 开始加入的特性，但 Java 在编译泛型代码后会执行<strong>泛型擦除</strong>的动作，即泛型信息在编译为字节码之后就丢失了，实际的类型都<strong>当做了 Object 类型</strong>来处理：</p><pre class="line-numbers language-java"><code class="language-java">List<span class="token operator">&lt;</span>Integer<span class="token operator">></span> list <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ArrayList</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>list<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 实际调用的是 List.add(Object e)</span>Integer x <span class="token operator">=</span> list<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 实际调用的是 Object obj = List.get(int index);</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>编译器真正生成的字节码中，还要额外做一个类型转换的操作：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// 需要将 Object 转为 Integer</span>Integer x <span class="token operator">=</span> <span class="token punctuation">(</span>Integer<span class="token punctuation">)</span>list<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>如果前面的 x 变量类型修改为 int 基本类型那么最终生成的字节码是：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// 需要将 Object 转为 Integer, 并执行拆箱操作</span><span class="token keyword">int</span> x <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>Integer<span class="token punctuation">)</span>list<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">intValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><h3 id="可变参数"><a href="#可变参数" class="headerlink" title="可变参数"></a>可变参数</h3><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Candy</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">foo</span><span class="token punctuation">(</span>String<span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>        String<span class="token punctuation">[</span><span class="token punctuation">]</span> array <span class="token operator">=</span> args<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 直接赋值</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>array<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token function">foo</span><span class="token punctuation">(</span><span class="token string">"hello"</span><span class="token punctuation">,</span> <span class="token string">"world"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>可变参数 <code>String... args</code> 其实是 <code>String[] args</code> ， Java 编译器会在编译期间将上述代码变换为：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token function">foo</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">String</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">{</span><span class="token string">"hello"</span><span class="token punctuation">,</span> <span class="token string">"world"</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>注：如果调用了 <code>foo()</code> 则等价代码为 <code>foo(new String[]&#123;&#125;)</code> ，创建了一个空的数组，而不会传递 null 进去</p><h3 id="foreach"><a href="#foreach" class="headerlink" title="foreach"></a>foreach</h3><p>数组的循环：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> array <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">,</span> <span class="token number">4</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 数组赋初值的简化写法也是语法糖</span><span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> e <span class="token operator">:</span> array<span class="token punctuation">)</span> <span class="token punctuation">{</span>    System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>编译后为循环取数：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> array<span class="token punctuation">.</span>length<span class="token punctuation">;</span> <span class="token operator">++</span>i<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">int</span> e <span class="token operator">=</span> array<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">;</span>    System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>集合的循环：</p><pre class="line-numbers language-java"><code class="language-java">List<span class="token operator">&lt;</span>Integer<span class="token operator">></span> list <span class="token operator">=</span> Arrays<span class="token punctuation">.</span><span class="token function">asList</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span><span class="token number">2</span><span class="token punctuation">,</span><span class="token number">3</span><span class="token punctuation">,</span><span class="token number">4</span><span class="token punctuation">,</span><span class="token number">5</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">for</span> <span class="token punctuation">(</span>Integer i <span class="token operator">:</span> list<span class="token punctuation">)</span> <span class="token punctuation">{</span>    System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>编译后转换为对迭代器的调用：</p><pre class="line-numbers language-java"><code class="language-java">List<span class="token operator">&lt;</span>Integer<span class="token operator">></span> list <span class="token operator">=</span> Arrays<span class="token punctuation">.</span><span class="token function">asList</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">,</span> <span class="token number">4</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">)</span><span class="token punctuation">;</span>Iterator iter <span class="token operator">=</span> list<span class="token punctuation">.</span><span class="token function">iterator</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">while</span><span class="token punctuation">(</span>iter<span class="token punctuation">.</span><span class="token function">hasNext</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    Integer e <span class="token operator">=</span> <span class="token punctuation">(</span>Integer<span class="token punctuation">)</span>iter<span class="token punctuation">.</span><span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>注：foreach 循环写法，能够配合数组以及所有实现了 Iterable 接口的集合类一起使用，其中 Iterable 用来获取集合的迭代器</p><h3 id="switch"><a href="#switch" class="headerlink" title="switch"></a>switch</h3><blockquote><p>switch 可以作用于字符串和枚举类：</p></blockquote><h4 id="字符串"><a href="#字符串" class="headerlink" title="字符串"></a>字符串</h4><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">switch</span> <span class="token punctuation">(</span>str<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">case</span> <span class="token string">"hello"</span><span class="token operator">:</span> <span class="token punctuation">{</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"h"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">break</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">case</span> <span class="token string">"world"</span><span class="token operator">:</span> <span class="token punctuation">{</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"w"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">break</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>注意：<strong>switch 配合 String 和枚举使用时，变量不能为 null</strong></p><p>会被编译器转换为：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">byte</span> x <span class="token operator">=</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span><span class="token keyword">switch</span><span class="token punctuation">(</span>str<span class="token punctuation">.</span><span class="token function">hashCode</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">case</span> <span class="token number">99162322</span><span class="token operator">:</span> <span class="token comment" spellcheck="true">// hello 的 hashCode</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>str<span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span><span class="token string">"hello"</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            x <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">break</span><span class="token punctuation">;</span>    <span class="token keyword">case</span> <span class="token number">113318802</span><span class="token operator">:</span> <span class="token comment" spellcheck="true">// world 的 hashCode</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>str<span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span><span class="token string">"world"</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            x <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token keyword">switch</span><span class="token punctuation">(</span>x<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">case</span> <span class="token number">0</span><span class="token operator">:</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"h"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">break</span><span class="token punctuation">;</span>    <span class="token keyword">case</span> <span class="token number">1</span><span class="token operator">:</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"w"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">break</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>总结：</p><ul><li>执行了两遍 switch，第一遍是根据字符串的 hashCode 和 equals 将字符串的转换为相应 byte 类型，第二遍才是利用 byte 执行进行比较</li><li>hashCode 是为了提高效率，减少可能的比较；而 equals 是为了防止 hashCode 冲突</li></ul><h4 id="枚举"><a href="#枚举" class="headerlink" title="枚举"></a>枚举</h4><p>switch 枚举的例子，原始代码：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">enum</span> Sex <span class="token punctuation">{</span>    MALE<span class="token punctuation">,</span> FEMALE<span class="token punctuation">}</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Candy</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">foo</span><span class="token punctuation">(</span>Sex sex<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">switch</span> <span class="token punctuation">(</span>sex<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token keyword">case</span> MALE<span class="token operator">:</span>                System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"男"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                 <span class="token keyword">break</span><span class="token punctuation">;</span>            <span class="token keyword">case</span> FEMALE<span class="token operator">:</span>                System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"女"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                 <span class="token keyword">break</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>编译转换后的代码：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">/*** 定义一个合成类（仅 jvm 使用，对我们不可见）* 用来映射枚举的 ordinal 与数组元素的关系* 枚举的 ordinal 表示枚举对象的序号，从 0 开始* 即 MALE 的 ordinal()=0，FEMALE 的 ordinal()=1*/</span><span class="token keyword">static</span> <span class="token keyword">class</span> $MAP <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 数组大小即为枚举元素个数，里面存储 case 用来对比的数字</span>    <span class="token keyword">static</span> <span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> map <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">int</span><span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span><span class="token punctuation">;</span>    <span class="token keyword">static</span> <span class="token punctuation">{</span>        map<span class="token punctuation">[</span>Sex<span class="token punctuation">.</span>MALE<span class="token punctuation">.</span><span class="token function">ordinal</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>        map<span class="token punctuation">[</span>Sex<span class="token punctuation">.</span>FEMALE<span class="token punctuation">.</span><span class="token function">ordinal</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token number">2</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">foo</span><span class="token punctuation">(</span>Sex sex<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">int</span> x <span class="token operator">=</span> $MAP<span class="token punctuation">.</span>map<span class="token punctuation">[</span>sex<span class="token punctuation">.</span><span class="token function">ordinal</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">;</span>    <span class="token keyword">switch</span> <span class="token punctuation">(</span>x<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">case</span> <span class="token number">1</span><span class="token operator">:</span>            System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"男"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">break</span><span class="token punctuation">;</span>        <span class="token keyword">case</span> <span class="token number">2</span><span class="token operator">:</span>            System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"女"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">break</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="枚举类"><a href="#枚举类" class="headerlink" title="枚举类"></a>枚举类</h3><p>JDK 7 新增了枚举类：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">enum</span> Sex <span class="token punctuation">{</span>    MALE<span class="token punctuation">,</span> FEMALE<span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>编译转换后：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token keyword">class</span> <span class="token class-name">Sex</span> <span class="token keyword">extends</span> <span class="token class-name">Enum</span><span class="token operator">&lt;</span>Sex<span class="token operator">></span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> Sex MALE<span class="token punctuation">;</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> Sex FEMALE<span class="token punctuation">;</span>    <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> Sex<span class="token punctuation">[</span><span class="token punctuation">]</span> $VALUES<span class="token punctuation">;</span>    <span class="token keyword">static</span> <span class="token punctuation">{</span>        MALE <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Sex</span><span class="token punctuation">(</span><span class="token string">"MALE"</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        FEMALE <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Sex</span><span class="token punctuation">(</span><span class="token string">"FEMALE"</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        $VALUES <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Sex</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">{</span>MALE<span class="token punctuation">,</span> FEMALE<span class="token punctuation">}</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">private</span> <span class="token function">Sex</span><span class="token punctuation">(</span>String name<span class="token punctuation">,</span> <span class="token keyword">int</span> ordinal<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">super</span><span class="token punctuation">(</span>name<span class="token punctuation">,</span> ordinal<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> Sex<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token function">values</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">return</span> $VALUES<span class="token punctuation">.</span><span class="token function">clone</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> Sex <span class="token function">valueOf</span><span class="token punctuation">(</span>String name<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">return</span> Enum<span class="token punctuation">.</span><span class="token function">valueOf</span><span class="token punctuation">(</span>Sex<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">,</span> name<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="try-w-r"><a href="#try-w-r" class="headerlink" title="try-w-r"></a>try-w-r</h3><p>JDK 7 开始新增了对需要关闭的资源处理的特殊语法 <code>try-with-resources</code>，格式：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">try</span><span class="token punctuation">(</span>资源变量 <span class="token operator">=</span> 创建资源对象<span class="token punctuation">)</span><span class="token punctuation">{</span><span class="token punctuation">}</span> <span class="token keyword">catch</span><span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>其中资源对象需要实现 <strong>AutoCloseable</strong> 接口，例如 InputStream、OutputStream、Connection、Statement、ResultSet 等接口都实现了 AutoCloseable ，使用 try-withresources可以不用写 finally 语句块，编译器会帮助生成关闭资源代码：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">try</span><span class="token punctuation">(</span>InputStream is <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">FileInputStream</span><span class="token punctuation">(</span><span class="token string">"a.txt"</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>is<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">IOException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span>    e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>转换成：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">try</span> <span class="token punctuation">{</span>    InputStream is <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">FileInputStream</span><span class="token punctuation">(</span><span class="token string">"a.txt"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    Throwable t <span class="token operator">=</span> null<span class="token punctuation">;</span>    <span class="token keyword">try</span> <span class="token punctuation">{</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>is<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Throwable</span> e1<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// t 是我们代码出现的异常</span>        t <span class="token operator">=</span> e1<span class="token punctuation">;</span>        <span class="token keyword">throw</span> e1<span class="token punctuation">;</span>    <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 判断了资源不为空</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>is <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 如果我们代码有异常</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span>t <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token keyword">try</span> <span class="token punctuation">{</span>                    is<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Throwable</span> e2<span class="token punctuation">)</span> <span class="token punctuation">{</span>                    <span class="token comment" spellcheck="true">// 如果 close 出现异常，作为被压制异常添加</span>                    t<span class="token punctuation">.</span><span class="token function">addSuppressed</span><span class="token punctuation">(</span>e2<span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token punctuation">}</span>            <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>                <span class="token comment" spellcheck="true">// 如果我们代码没有异常，close 出现的异常就是最后 catch 块中的 e</span>                is<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">IOException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span>    e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><code>addSuppressed(Throwable e)</code>：添加被压制异常，是为了防止异常信息的丢失（<strong>fianlly 中如果抛出了异常</strong>）</p><h3 id="方法重写"><a href="#方法重写" class="headerlink" title="方法重写"></a>方法重写</h3><p>方法重写时对返回值分两种情况：</p><ul><li>父子类的返回值完全一致</li><li>子类返回值可以是父类返回值的子类</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">A</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> Number <span class="token function">m</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token keyword">return</span> <span class="token number">1</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token keyword">class</span> <span class="token class-name">B</span> <span class="token keyword">extends</span> <span class="token class-name">A</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Override</span>    <span class="token comment" spellcheck="true">// 子类m方法的返回值是Integer是父类m方法返回值Number的子类</span>    <span class="token keyword">public</span> Integer <span class="token function">m</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">return</span> <span class="token number">2</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>对于子类，Java 编译器会做如下处理：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">B</span> <span class="token keyword">extends</span> <span class="token class-name">A</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> Integer <span class="token function">m</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">return</span> <span class="token number">2</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">// 此方法才是真正重写了父类 public Number m() 方法</span>    <span class="token keyword">public</span> synthetic bridge Number <span class="token function">m</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 调用 public Integer m()</span>        <span class="token keyword">return</span> <span class="token function">m</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>其中桥接方法比较特殊，仅对 Java 虚拟机可见，并且与原来的 public Integer m() 没有命名冲突</p><h3 id="匿名内部类"><a href="#匿名内部类" class="headerlink" title="匿名内部类"></a>匿名内部类</h3><h4 id="无参优化"><a href="#无参优化" class="headerlink" title="无参优化"></a>无参优化</h4><p>源代码：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Candy</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>        Runnable runnable <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Runnable</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token annotation punctuation">@Override</span>            <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">run</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"ok"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>转化后代码：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// 额外生成的类</span><span class="token keyword">final</span> <span class="token keyword">class</span> <span class="token class-name">Candy</span>$<span class="token number">1</span> <span class="token keyword">implements</span> <span class="token class-name">Runnable</span> <span class="token punctuation">{</span>    Candy$<span class="token function">1</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token punctuation">}</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">run</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"ok"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Candy</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>        Runnable runnable <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Candy</span>$<span class="token function">1</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="带参优化"><a href="#带参优化" class="headerlink" title="带参优化"></a>带参优化</h4><p>引用局部变量的匿名内部类，源代码：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Candy</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">test</span><span class="token punctuation">(</span><span class="token keyword">final</span> <span class="token keyword">int</span> x<span class="token punctuation">)</span> <span class="token punctuation">{</span>        Runnable runnable <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Runnable</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token annotation punctuation">@Override</span>            <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">run</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"ok:"</span> <span class="token operator">+</span> x<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>转换后代码：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">final</span> <span class="token keyword">class</span> <span class="token class-name">Candy</span>$<span class="token number">1</span> <span class="token keyword">implements</span> <span class="token class-name">Runnable</span> <span class="token punctuation">{</span>    <span class="token keyword">int</span> val$x<span class="token punctuation">;</span>    Candy$<span class="token function">1</span><span class="token punctuation">(</span><span class="token keyword">int</span> x<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">this</span><span class="token punctuation">.</span>val$x <span class="token operator">=</span> x<span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">run</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"ok:"</span> <span class="token operator">+</span> <span class="token keyword">this</span><span class="token punctuation">.</span>val$x<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Candy</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">test</span><span class="token punctuation">(</span><span class="token keyword">final</span> <span class="token keyword">int</span> x<span class="token punctuation">)</span> <span class="token punctuation">{</span>        Runnable runnable <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Candy</span>$<span class="token function">1</span><span class="token punctuation">(</span>x<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>局部变量在底层创建为内部类的成员变量，必须是 final 的原因：</p><ul><li><p>在 Java 中方法调用是值传递的，在匿名内部类中对变量的操作都是基于原变量的副本，不会影响到原变量的值，所以<strong>原变量的值的改变也无法同步到副本中</strong></p></li><li><p>外部变量为 final 是在编译期以强制手段确保用户不会在内部类中做修改原变量值的操作，也是<strong>防止外部操作修改了变量而内部类无法随之变化</strong>出现的影响</p><p>在创建 <code>Candy$1 </code> 对象时，将 x 的值赋值给了 <code>Candy$1</code> 对象的 val 属性，x 不应该再发生变化了，因为发生变化，this.val$x 属性没有机会再跟着变化</p></li></ul><h3 id="反射优化"><a href="#反射优化" class="headerlink" title="反射优化"></a>反射优化</h3><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Reflect1</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">foo</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"foo..."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token keyword">throws</span> Exception <span class="token punctuation">{</span>        Method foo <span class="token operator">=</span> Reflect1<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">.</span><span class="token function">getMethod</span><span class="token punctuation">(</span><span class="token string">"foo"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;=</span> <span class="token number">16</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">printf</span><span class="token punctuation">(</span><span class="token string">"%d\t"</span><span class="token punctuation">,</span> i<span class="token punctuation">)</span><span class="token punctuation">;</span>            foo<span class="token punctuation">.</span><span class="token function">invoke</span><span class="token punctuation">(</span>null<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        System<span class="token punctuation">.</span>in<span class="token punctuation">.</span><span class="token function">read</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>foo.invoke 0 ~ 15 次调用的是 MethodAccessor 的实现类 <code>NativeMethodAccessorImpl.invoke0()</code>，本地方法执行速度慢；当调用到第 16 次时，会采用运行时生成的类 <code>sun.reflect.GeneratedMethodAccessor1</code> 代替</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> Object <span class="token function">invoke</span><span class="token punctuation">(</span>Object obj<span class="token punctuation">,</span> Object<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span><span class="token keyword">throws</span> Exception <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// inflationThreshold 膨胀阈值，默认 15</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">++</span>numInvocations <span class="token operator">></span> ReflectionFactory<span class="token punctuation">.</span><span class="token function">inflationThreshold</span><span class="token punctuation">(</span><span class="token punctuation">)</span>        <span class="token operator">&amp;&amp;</span> <span class="token operator">!</span>ReflectUtil<span class="token punctuation">.</span><span class="token function">isVMAnonymousClass</span><span class="token punctuation">(</span>method<span class="token punctuation">.</span><span class="token function">getDeclaringClass</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        MethodAccessorImpl acc <span class="token operator">=</span> <span class="token punctuation">(</span>MethodAccessorImpl<span class="token punctuation">)</span>            <span class="token keyword">new</span> <span class="token class-name">MethodAccessorGenerator</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>            <span class="token function">generateMethod</span><span class="token punctuation">(</span>method<span class="token punctuation">.</span><span class="token function">getDeclaringClass</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>                           method<span class="token punctuation">.</span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>                           method<span class="token punctuation">.</span><span class="token function">getParameterTypes</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>                           method<span class="token punctuation">.</span><span class="token function">getReturnType</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>                           method<span class="token punctuation">.</span><span class="token function">getExceptionTypes</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>                           method<span class="token punctuation">.</span><span class="token function">getModifiers</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        parent<span class="token punctuation">.</span><span class="token function">setDelegate</span><span class="token punctuation">(</span>acc<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">// 【调用本地方法实现】</span>    <span class="token keyword">return</span> <span class="token function">invoke0</span><span class="token punctuation">(</span>method<span class="token punctuation">,</span> obj<span class="token punctuation">,</span> args<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">native</span> Object <span class="token function">invoke0</span><span class="token punctuation">(</span>Method m<span class="token punctuation">,</span> Object obj<span class="token punctuation">,</span> Object<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">GeneratedMethodAccessor1</span> <span class="token keyword">extends</span> <span class="token class-name">MethodAccessorImpl</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 如果有参数，那么抛非法参数异常</span>    block4 <span class="token operator">:</span> <span class="token punctuation">{</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>arrobject <span class="token operator">==</span> null <span class="token operator">||</span> arrobject<span class="token punctuation">.</span>length <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">break</span> block4<span class="token punctuation">;</span>            <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">IllegalArgumentException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">try</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 【可以看到，已经是直接调用方法】</span>        Reflect1<span class="token punctuation">.</span><span class="token function">foo</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 因为没有返回值</span>        <span class="token keyword">return</span> null<span class="token punctuation">;</span>    <span class="token punctuation">}</span>   <span class="token comment" spellcheck="true">//....</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>通过查看 ReflectionFactory 源码可知：</p><ul><li>sun.reflect.noInflation 可以用来禁用膨胀，直接生成 GeneratedMethodAccessor1，但首次生成比较耗时，如果仅反射调用一次，不划算</li><li>sun.reflect.inflationThreshold 可以修改膨胀阈值</li></ul><h2 id="❷运行期优化-JIT优化"><a href="#❷运行期优化-JIT优化" class="headerlink" title="❷运行期优化(JIT优化)"></a>❷运行期优化(JIT优化)</h2><blockquote><p>即时编译器除了将字节码编译为本地机器码外，还会对代码进行一定程度的优化，它包含多达几十种优化技术，这里选取其中代表性的进行介绍：</p></blockquote><h3 id="逃逸分析"><a href="#逃逸分析" class="headerlink" title="逃逸分析"></a>逃逸分析</h3><p>逃逸分析并不是直接的优化手段，而是一个代码分析方式，通过动态分析对象的作用域，为优化手段如栈上分配、标量替换和同步消除等提供依据，发生逃逸行为的情况有两种：方法逃逸和线程逃逸</p><ul><li>方法逃逸：当一个对象在方法中定义之后，被外部方法引用<ul><li>全局逃逸：一个对象的作用范围逃出了当前方法或者当前线程，比如对象是一个静态变量、全局变量赋值、已经发生逃逸的对象、作为当前方法的返回值</li><li>参数逃逸：一个对象被作为方法参数传递或者被参数引用</li></ul></li><li>线程逃逸：如类变量或实例变量，可能被其它线程访问到</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">static</span> StringBuilder <span class="token function">concat</span><span class="token punctuation">(</span>String<span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span> strings<span class="token punctuation">)</span> <span class="token punctuation">{</span>    StringBuilder sb <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">StringBuilder</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">for</span> <span class="token punctuation">(</span>String string <span class="token operator">:</span> strings<span class="token punctuation">)</span> <span class="token punctuation">{</span>        sb<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span>string<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">return</span> sb<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 发生了方法逃逸</span><span class="token punctuation">}</span><span class="token keyword">public</span> <span class="token keyword">static</span> String <span class="token function">concat</span><span class="token punctuation">(</span>String<span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span> strings<span class="token punctuation">)</span> <span class="token punctuation">{</span>    StringBuilder sb <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">StringBuilder</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">for</span> <span class="token punctuation">(</span>String string <span class="token operator">:</span> strings<span class="token punctuation">)</span> <span class="token punctuation">{</span>        sb<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span>string<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">return</span> sb<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 没有发生方法逃逸</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>如果不存在逃逸行为，则可以对该对象进行如下优化：同步消除、标量替换和栈上分配</p><ul><li>**栈上分配 (Stack Allocations)**：如果一个对象不会逃逸到线程外，那么将会在栈上分配内存来创建这个对象，而不是 Java 堆上，此时对象所占用的内存空间就会随着栈帧的出栈而销毁，从而可以减轻垃圾回收的压力。</li><li>**标量替换 (Scalar Replacement)**：如果一个数据已经无法再分解成为更小的数据类型，那么这些数据就称为标量（如 int、long 等数值类型及 reference 类型等）；反之，如果一个数据可以继续分解，那它就被称为聚合量（如对象）。如果一个对象不会逃逸外方法外，那么就可以将其改为直接创建若干个被这个方法使用的成员变量来替代，从而减少内存占用。<ul><li><code>-XX:+EliminateAllocations</code>：开启标量替换</li><li><code>-XX:+PrintEliminateAllocations</code>：查看标量替换情况</li></ul></li><li><strong>同步消除 (Synchronization Elimination)<strong>：线程同步本身比较耗时，如果确定一个对象不会逃逸出线程，不被其它线程访问到，那对象的读写就不会存在竞争，则可以消除对该对象的</strong>同步锁</strong>，通过 <code>-XX:+EliminateLocks</code> 可以开启同步消除 ( - 号关闭)</li></ul><h3 id="方法内联"><a href="#方法内联" class="headerlink" title="方法内联"></a>方法内联</h3><p>方法内联：<strong>将调用的函数代码编译到调用点处</strong>，这样可以减少栈帧的生成，减少参数传递以及跳转过程</p><p>方法内联能够消除方法调用的固定开销，任何方法除非被内联，否则调用都会有固定开销，来源于保存程序在该方法中的执行位置，以及新建、压入和弹出新方法所使用的栈帧。</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">int</span> <span class="token function">square</span><span class="token punctuation">(</span><span class="token keyword">final</span> <span class="token keyword">int</span> i<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">return</span> i <span class="token operator">*</span> i<span class="token punctuation">;</span><span class="token punctuation">}</span>System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token function">square</span><span class="token punctuation">(</span><span class="token number">9</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>square 是热点方法，会进行内联，把方法内代码拷贝粘贴到调用者的位置：</p><pre class="line-numbers language-java"><code class="language-java">System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token number">9</span> <span class="token operator">*</span> <span class="token number">9</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>还能够进行常量折叠（constant folding）的优化：</p><pre class="line-numbers language-java"><code class="language-java">System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token number">81</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><ul><li><p>冗余消除：根据运行时状况进行代码折叠或削除</p></li><li><p>内联缓存：是一种加快动态绑定的优化技术（方法调用部分详解）</p></li></ul><h3 id="公共子表达式消除"><a href="#公共子表达式消除" class="headerlink" title="公共子表达式消除"></a>公共子表达式消除</h3><p>如果一个表达式 E 之前已经被计算过了，并且从先前的计算到现在 E 中所有变量的值都没有发生过变化，那么 E 这次的出现就称为公共子表达式。对于这种表达式，无需再重新进行计算，只需要直接使用前面的计算结果即可。</p><h3 id="数组边界检查消除"><a href="#数组边界检查消除" class="headerlink" title="数组边界检查消除"></a>数组边界检查消除</h3><p>对于虚拟机执行子系统来说，每次数组元素的读写都带有一次隐含的上下文检查以避免访问越界。如果数组的访问发生在循环之中，并且使用循环变量来访问数据，即循环变量的取值永远在 [0，list.length) 之间，那么此时就可以消除整个循环的数据边界检查，从而避免多次无用的判断。</p><h1 id="⑦性能监控-amp-调优"><a href="#⑦性能监控-amp-调优" class="headerlink" title="⑦性能监控&amp;调优"></a>⑦性能监控&amp;调优</h1><h2 id="❶监控-amp-诊断工具"><a href="#❶监控-amp-诊断工具" class="headerlink" title="❶监控&amp;诊断工具"></a>❶监控&amp;诊断工具</h2><h3 id="命令行工具"><a href="#命令行工具" class="headerlink" title="命令行工具"></a>命令行工具</h3><blockquote><p>参考：<a href="https://tobebetterjavaer.com/jvm/problem-tools.html#%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4">Java问题诊断和排查工具</a></p></blockquote><p><img src="/../images/Java-JVM/problem-tools-01.png"></p><ul><li><p>jps：查看正在运行的Java进程</p></li><li><p>jstat：查看JVM统计信息</p></li><li><p>jinfo：实时查看和修改JVM配置参数</p></li><li><p>jmap：导出内存映像文件&amp;内存使用情况</p></li><li><p>jhat：JDK自带堆分析工具</p></li><li><p>jstack：打印JVM中线程快照</p></li><li><p>jcmd：多功能命令行，可以用来实现前面除了jstat之外所有命令的功能</p></li><li><p>jstatd：远程主机信息收集</p></li></ul><h3 id="GUI工具"><a href="#GUI工具" class="headerlink" title="GUI工具"></a>GUI工具</h3><blockquote><p>参考：<a href="https://www.yuque.com/u21195183/jvm/lv1zot#KYSGY">JVM监控及诊断工具-GUI篇</a></p></blockquote><h4 id="JConsole"><a href="#JConsole" class="headerlink" title="JConsole"></a>JConsole</h4><p>从Java5开始，在JDK中自带的Java监控和管理控制台。用于对JVM中内存、线程和类等的监控。</p><h4 id="Visual-VM"><a href="#Visual-VM" class="headerlink" title="Visual VM"></a>Visual VM</h4><p>多功能的监测工具，可以连续监测，集成了多个JDK命令行工具，用于显示虚拟机进程及进程的配置和环境信息（jps，jinfo），监视应用程序的CPU、GC、堆、方法区及线程的信息（jstat、jstack）等，甚至代替JConsole。</p><ul><li><p>官网：<a href="https://visualvm.github.io/">VisualVM: Home</a></p></li><li><p>教程：<a href="https://www.bilibili.com/video/BV1PJ411n7xZ?p=322&vd_source=fa98a6d1417d05689c64bc1449966321">VisualVM的安装及连接方式</a></p></li></ul><h4 id="JProfiler"><a href="#JProfiler" class="headerlink" title="JProfiler"></a>JProfiler</h4><p>主要功能：</p><p>1-方法调用：对方法调用的分析可以帮助您了解应用程序正在做什么，并找到提高其性能的方法</p><p>2-内存分配：通过分析堆上对象、引用链和垃圾收集能帮您修复内存泄露问题，优化内存使用</p><p>3-线程和锁：JProfiler提供多种针对线程和锁的分析视图助您发现多线程问题</p><p>4-高级子系统：许多性能问题都发生在更高的语义级别上。例如，对于JDBC调用，您可能希望找出执行最慢的SQL语句。JProfiler支持对这些子系统进行集成分析</p><ul><li><p>官网：<a href="https://www.ej-technologies.com/products/jprofiler/overview.html">Java Profiler - JProfiler (ej-technologies.com)</a></p></li><li><p>教程：<a href="https://www.bilibili.com/video/BV1PJ411n7xZ?p=340&vd_source=fa98a6d1417d05689c64bc1449966321">JProfiler的使用概述</a></p></li></ul><h4 id="Arthas"><a href="#Arthas" class="headerlink" title="Arthas"></a>Arthas</h4><p>Arthas是Alibaba开源的Java诊断工具</p><h4 id="HSDB"><a href="#HSDB" class="headerlink" title="HSDB"></a>HSDB</h4><p>JDK自带的工具，用于查看JVM运行时的状态</p><pre class="line-numbers language-bash"><code class="language-bash">java -cp /Library/Java/JavaVirtualMachines/jdk1.8.0_351.jdk/Contents/Home/lib/sa-jdi.jar sun.jvm.hotspot.HSDB<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><h2 id="❷JVM运行时参数"><a href="#❷JVM运行时参数" class="headerlink" title="❷JVM运行时参数"></a>❷JVM运行时参数</h2><p>JVM参数⼤致可以分为三类：</p><ol><li><p>标注指令： -开头，所有的JVM实现都必须实现这些参数的功能，而且向后兼容。可以⽤ java -help 打印出来。</p></li><li><p>⾮标准指令： -X开头，默认jvm实现这些参数的功能，但是并不保证所有jvm实现都满足，且不保证向后兼容。可以⽤ java -X 打印出来。</p></li><li><p>非稳定参数： -XX 开头，此类参数各个jvm实现会有所不同，将来可能会随时取消，需要慎重使用；</p><pre class="line-numbers language-java"><code class="language-java">java <span class="token operator">-</span>XX<span class="token operator">:</span><span class="token operator">+</span>PrintCommandLineFlags  <span class="token comment" spellcheck="true">// 查看当前JVM的不稳定指令。</span>java <span class="token operator">-</span>XX<span class="token operator">:</span><span class="token operator">+</span>PrintFlagsInitial     <span class="token comment" spellcheck="true">// 查看所有不稳定指令的默认值。</span>java <span class="token operator">-</span>XX<span class="token operator">:</span><span class="token operator">+</span>PrintFlagsFinal       <span class="token comment" spellcheck="true">// 查看所有不稳定指令最终⽣效的实际值。</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre></li></ol><ul><li><p>设置堆内存指令：<code>-Xmx Size</code></p></li><li><p>设置栈内存大小：<code>-Xss size</code>   </p></li><li><p><code>-XX:StringTableSize=个数</code> StringTable桶个数</p></li><li><p><code>-XX:MaxTenuringThreshold</code>：定义年龄的阈值，默认是 15</p></li><li><p><code>-XX:PretenureSizeThreshold</code>：大于此值的对象直接在老年代分配</p></li><li><p><code>-XX:MaxMetaspaceSize=size</code>元空间大小设置</p></li><li><p><code>-XX:MaxTenuringThreshold</code> 调大对象进入老年代的年龄，让对象在新生代多存活一段时间</p></li></ul><ul><li><p><code>-XX:UseTLAB</code>：设置是否开启 TLAB 空间</p></li><li><p><code>-XX:TLABWasteTargetPercent</code>：设置 TLAB 空间所占用 Eden 空间的百分比大小，默认情况下 TLAB 空间的内存非常小，仅占有整个 Eden 空间的1%</p></li><li><p><code>-XX:TLABRefillWasteFraction</code>：指当 TLAB 空间不足，请求分配的对象内存大小超过此阈值时不会进行 TLAB 分配，直接进行堆内存分配，否则还是会优先进行 TLAB 分配</p></li><li><p><code>-XX:+EliminateAllocations</code>：开启标量替换</p></li><li><p><code>-XX:+PrintEliminateAllocations</code>：查看标量替换情况</p></li></ul><ul><li>-XX:PremSize：设置永久代的初始大小</li><li>-XX:MaxPermSize: 设置永久代的最大值</li></ul><p><em>含义参数</em></p><p><em>堆初始大小-Xms</em></p><p><em>堆最大大小-Xmx 或 -XX:MaxHeapSize&#x3D;size</em></p><p><em>新生代大小-Xmn 或 (-XX:NewSize&#x3D;size + -XX:MaxNewSize&#x3D;size )</em></p><p><em>幸存区比例（动态） -XX:InitialSurvivorRatio&#x3D;ratio 和 -XX:+UseAdaptiveSizePolicy</em></p><p><em>幸存区比例-XX:SurvivorRatio&#x3D;ratio</em></p><p><em>晋升阈值-XX:MaxTenuringThreshold&#x3D;threshold</em></p><p><em>晋升详情-XX:+PrintTenuringDistribution</em></p><p><em>GC详情-XX:+PrintGCDetails -verbose:gc</em></p><p><em>FullGC 前 MinorGC -XX:+ScavengeBeforeFullGC</em></p><h2 id="❸GC日志"><a href="#❸GC日志" class="headerlink" title="❸GC日志"></a>❸GC日志</h2><h1 id="📚参考资料"><a href="#📚参考资料" class="headerlink" title="📚参考资料"></a>📚参考资料</h1><ul><li><p><a href="https://blog.csdn.net/weixin_43715214/article/details/126694782">Java虚拟机（JVM）面试专题 上</a></p></li><li><p><a href="https://blog.csdn.net/weixin_50280576/article/details/113742011">黑马JVM 学习笔记</a></p></li><li><p><a href="https://www.yuque.com/u21195183/jvm/qpoa81">尚硅谷JVM学习笔记</a></p></li><li><p><a href="https://blog.csdn.net/weixin_32265569/article/details/107575286">黑马JVM学习笔记</a></p></li><li><p><a href="https://github.com/Seazean/JavaNote">Seazean&#x2F;JavaNote: </a></p></li><li><p><a href="http://wangwren.com/2019/11/JVM%E5%86%85%E5%AD%98%E6%A8%A1%E5%9E%8B/#JVM%E7%9A%84%E8%BF%90%E8%A1%8C%E5%8E%9F%E7%90%86">JVM的运行原理</a></p></li><li><p><a href="https://www.jianshu.com/p/12544c0ad5c1">三色标记法与读写屏障 - 简书 (jianshu.com)</a></p></li><li><p><a href="https://github.com/doocs/jvm">Java 虚拟机底层原理知识总结</a></p></li><li><p><a href="https://tobebetterjavaer.com/jvm/zongjie.html#%E5%85%AB%E3%80%81%E4%BB%A3%E7%A0%81%E4%BC%98%E5%8C%96">JVM 核心知识点总结</a></p></li></ul><h1 id="❤️Sponsor"><a href="#❤️Sponsor" class="headerlink" title="❤️Sponsor"></a>❤️Sponsor</h1><p>您的支持是我不断前进的动力，如果您感觉本文对您有所帮助的话，可以考虑打赏一下本文，用以维持本博客的运营费用，拒绝白嫖，从你我做起！🥰🥰🥰</p><table>  <tbody>     <tr>         <td style="text-align:center;">支付宝</td>         <td style="text-align:center;">微信</td>     </tr>   <tr>    <td style="text-align:center;" ><img width="200" src="https://jwt1399.top/medias/reward/alipay.png"></td>          <td style="text-align:center;"><img width="200" src="https://jwt1399.top/medias/reward/sponor_wechat.png"></td>       </tr></tbody></table>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;h1 id=&quot;①JVM概述&quot;&gt;&lt;a href=&quot;#①JVM概述&quot; class=&quot;headerlink&quot; title=&quot;①JVM概述&quot;&gt;&lt;/a&gt;①JVM概述&lt;/h1&gt;&lt;h2 id=&quot;❶基本介绍&quot;&gt;&lt;a href=&quot;#❶基本介绍&quot; class=&quot;headerlink&quot;</summary>
        
      
    
    
    
    <category term="JavaSE" scheme="https://jwt1399.top/categories/JavaSE/"/>
    
    
    <category term="JVM" scheme="https://jwt1399.top/tags/JVM/"/>
    
  </entry>
  
  <entry>
    <title>Java-设计模式</title>
    <link href="https://jwt1399.top/posts/50902.html"/>
    <id>https://jwt1399.top/posts/50902.html</id>
    <published>2022-11-07T15:38:34.000Z</published>
    <updated>2023-07-03T07:29:24.487Z</updated>
    
    <content type="html"><![CDATA[<h1 id="⓪设计模式基础"><a href="#⓪设计模式基础" class="headerlink" title="⓪设计模式基础"></a>⓪设计模式基础</h1><h2 id="❶设计模式分类"><a href="#❶设计模式分类" class="headerlink" title="❶设计模式分类"></a>❶设计模式分类</h2><ul><li><strong>创建型模式</strong></li></ul><p>用于描述<strong>对象实例化（创建对象）的模式，即用于解耦对象的实例化过程</strong></p><p>GoF（四人组）书中提供了单例、原型、工厂方法、抽象工厂、建造者等 <strong>5 种创建型模式</strong>。</p><ul><li><strong>结构型模式</strong></li></ul><p>用于描述如何<strong>把类或对象结合在一起形成一个更大的结构</strong>，</p><p>GoF（四人组）书中提供了代理、适配器、桥接、装饰、外观、享元、组合等 <strong>7 种结构型模式</strong>。</p><ul><li><strong>行为型模式</strong></li></ul><p>用于描述<strong>类和对象如何交互，及划分责任和算法</strong>。</p><p>GoF（四人组）书中提供了模板方法、策略、命令、职责链、状态、观察者、中介者、迭代器、访问者、备忘录、解释器等 <strong>11 种行为型模式</strong>。</p><h2 id="❷UML-类图"><a href="#❷UML-类图" class="headerlink" title="❷UML-类图"></a>❷UML-类图</h2><blockquote><p>统一建模语言（Unified Modeling Language，UML）是用来设计软件的可视化建模语言。它的特点是简单、统一、图形化、能表达软件设计中的动态与静态信息。UML 从目标系统的不同角度出发，定义了用例图、类图、对象图、状态图、活动图、时序图、协作图、构件图、部署图等 9 种图。</p></blockquote><p>类图(Class diagram)是显示了模型的静态结构，特别是模型中存在的类、类的内部结构以及它们与其他类的关系等。类图不显示暂时性的信息。类图是面向对象建模的主要组成部分。</p><h3 id="0-类图表示法"><a href="#0-类图表示法" class="headerlink" title="0.类图表示法"></a>0.类图表示法</h3><p>在UML类图中，类使用包含类名、属性(field) 和方法(method) 且带有分割线的矩形来表示，比如下图表示一个Employee类，它包含name,age和address这3个属性，以及work()方法。 </p><p><img src="https://img.jwt1399.top/img/202211082308467.png"></p><p>属性&#x2F;方法名称前加的加号和减号表示了这个属性&#x2F;方法的可见性，UML类图中表示可见性的符号有三种：</p><ul><li><p>+：表示public</p></li><li><p>-：表示private</p></li><li><p>#：表示protected</p></li><li><p>什么都不加表示default</p></li></ul><p>属性的完整表示方式是： <strong>可见性  名称 ：类型 [ &#x3D; 缺省值]</strong>  </p><p>方法的完整表示方式是： <strong>可见性  名称(参数列表) [ ： 返回类型]</strong></p><blockquote><p>注意：</p><p>​1，中括号中的内容表示是可选的</p><p>​2，也有将类型放在变量名前面，返回值类型放在方法名前面</p></blockquote><p><img src="https://img.jwt1399.top/img/202211082325582.png" alt="类的关系"></p><h3 id="1-关联关系"><a href="#1-关联关系" class="headerlink" title="1.关联关系"></a>1.关联关系</h3><blockquote><p>通常关联关系用来实现连接有关联的对象所对应的类，即将一个类的对象作为另一个类的属性。关联又可以分为单向关联，双向关联，自关联。</p></blockquote><h4 id="a-单向关联"><a href="#a-单向关联" class="headerlink" title="a.单向关联"></a><strong>a.单向关联</strong></h4><p>单向关联用一个带箭头的实线表示。下图表示每个顾客都有一个地址，这通过让Customer类持有一个类型为Address的成员变量类实现。</p><p><img src="https://img.jwt1399.top/img/202211082320339.png"></p><h4 id="b-双向关联"><a href="#b-双向关联" class="headerlink" title="b.双向关联"></a><strong>b.双向关联</strong></h4><p>双向关联就是双方各自持有对方类型的成员变量。双向关联用一个不带箭头的直线表示。下图中在Customer类中维护一个List&lt;Product&gt;，表示一个顾客可以购买多个商品；在Product类中维护一个Customer类型的成员变量表示这个产品被哪个顾客所购买。</p><p><img src="https://img.jwt1399.top/img/202211082320373.png"></p><h4 id="c-自关联"><a href="#c-自关联" class="headerlink" title="c.自关联"></a><strong>c.自关联</strong></h4><p>自关联用一个带有箭头且指向自身的线表示。下图的意思就是Node类包含类型为Node的成员变量，也就是“自己包含自己”。</p><p><img src="https://img.jwt1399.top/img/202211082320886.png"></p><h3 id="2-聚合关系"><a href="#2-聚合关系" class="headerlink" title="2.聚合关系"></a>2.聚合关系</h3><blockquote><p>聚合关系表示整体与部分的关系，成员对象是整体对象的一部分，但是成员对象可以脱离整体对象而独立存在。例如，学校与老师的关系，学校包含老师，但如果学校停办了，老师依然存在。</p></blockquote><p>聚合关系可以用带空心菱形的实线来表示，菱形指向整体。下图所示是大学和教师的关系图：</p><p><img src="https://img.jwt1399.top/img/202211082328836.png"></p><h3 id="3-组合关系"><a href="#3-组合关系" class="headerlink" title="3.组合关系"></a>3.组合关系</h3><blockquote><p>组合表示类之间的整体与部分的关系，但它是一种更强烈的聚合关系。在组合关系中，整体对象可以控制部分对象的生命周期，一旦整体对象不存在，部分对象也将不存在，部分对象不能脱离整体对象而存在。例如，头和嘴的关系，没有了头，嘴也就不存在了。</p></blockquote><p>组合关系用带实心菱形的实线来表示，菱形指向整体。下图所示是头和嘴的关系图：</p><p><img src="https://img.jwt1399.top/img/202211082330291.png"></p><h3 id="4-依赖关系"><a href="#4-依赖关系" class="headerlink" title="4.依赖关系"></a>4.依赖关系</h3><blockquote><p>依赖关系是一种使用关系，它是对象之间耦合度最弱的一种关联方式，是临时性的关联。在代码中，某个类的方法通过局部变量、方法的参数或者对静态方法的调用来访问另一个类（被依赖类）中的某些方法来完成一些职责。</p></blockquote><p>依赖关系使用带箭头的虚线来表示，箭头从使用类指向被依赖的类。下图所示是司机和汽车的关系图，司机驾驶汽车：</p><p><img src="https://img.jwt1399.top/img/202211082332399.png"></p><p>从图中可以看出Driver中使用了Car的move方法。那么就说明Driver是依赖于Car才能做Driver的职责。那么又有人会问聚合与依赖有区别吗，当然很明显Driver是一个整体，Car也是整体。不是整体与部分关系。</p><h3 id="5-继承关系"><a href="#5-继承关系" class="headerlink" title="5.继承关系"></a>5.继承关系</h3><blockquote><p>继承关系是对象之间耦合度最大的一种关系，表示一般与特殊的关系，是父类与子类之间的关系，是一种继承关系。</p></blockquote><p>泛化(继承)关系用带空心三角箭头的实线来表示，箭头从子类指向父类。例如，Student 类和 Teacher 类都是 Person 类的子类，其类图如下图所示：</p><p><img src="https://img.jwt1399.top/img/202211082333096.png"></p><h3 id="6-实现关系"><a href="#6-实现关系" class="headerlink" title="6.实现关系"></a>6.实现关系</h3><blockquote><p>实现关系是接口与实现类之间的关系。在这种关系中，类实现了接口，类中的操作实现了接口中所声明的所有的抽象操作。</p></blockquote><p>实现关系使用带空心三角箭头的虚线来表示，箭头从实现类指向接口。例如，汽车和船实现了交通工具，其类图如图所示。</p><p><img src="https://img.jwt1399.top/img/202211082334819.png"></p><h2 id="❸软件设计原则"><a href="#❸软件设计原则" class="headerlink" title="❸软件设计原则"></a>❸软件设计原则</h2><blockquote><p>在软件开发中，为了提高软件系统的可维护性和可复用性，增加软件的可扩展性和灵活性，程序员要尽量根据6条原则来开发程序，从而提高软件开发效率、节约软件开发成本和维护成本。</p></blockquote><h3 id="1-开闭原则（OCP）"><a href="#1-开闭原则（OCP）" class="headerlink" title="1.开闭原则（OCP）"></a>1.开闭原则（OCP）</h3><p><strong>对扩展开放，对修改关闭</strong>。在程序需要进行拓展的时候，不能去修改原有的代码，实现一个热插拔的效果。简言之，是为了使程序的扩展性好，易于维护和升级。想要达到这样的效果，我们需要使用接口和抽象类。</p><p>因为抽象灵活性好，适应性广，只要抽象的合理，可以基本保持软件架构的稳定。而软件中易变的细节可以从抽象派生来的实现类来进行扩展，当软件需要发生变化时，只需要根据需求重新派生一个实现类来扩展就可以了。</p><p>【例】以 <code>搜狗输入法</code> 的皮肤为例介绍开闭原则的应用。</p><p>分析：<code>搜狗输入法</code> 的皮肤是输入法背景图片、窗口颜色和声音等元素的组合。用户可以根据自己的喜爱更换自己的输入法的皮肤，也可以从网上下载新的皮肤。这些皮肤有共同的特点，可以为其定义一个抽象类（AbstractSkin），而每个具体的皮肤（DefaultSpecificSkin和HeimaSpecificSkin）是其子类。用户窗体可以根据需要选择或者增加新的主题，而不需要修改原代码，所以它是满足开闭原则的。<img src="https://img.jwt1399.top/img/202211082338060.png"></p><h3 id="2-里氏代换原则（LSP）"><a href="#2-里氏代换原则（LSP）" class="headerlink" title="2.里氏代换原则（LSP）"></a>2.里氏代换原则（LSP）</h3><p>子类可以扩展父类的功能，但不能改变父类原有的功能。换句话说，子类继承父类时，除添加新的方法完成新增功能外，尽量不要重写父类的方法。如果通过重写父类的方法来完成新的功能，这样写起来虽然简单，但是整个继承体系的可复用性会比较差，特别是运用多态比较频繁时，程序运行出错的概率会非常大。</p><p>【例】正方形不是长方形。</p><p>在数学领域里，正方形毫无疑问是长方形，它是一个长宽相等的长方形。所以，我们开发的一个与几何图形相关的软件系统，就可以顺理成章的让正方形继承自长方形。</p><p><img src="https://img.jwt1399.top/img/202211082347129.png"></p><p>长方形类 Rectangle，正方形类 Square，resize方法是RectandleDemo类中的方法，用来实现宽度逐渐增长的效果。</p><ul><li><p>假如我们把一个普通长方形作为参数传入resize方法，就会看到长方形宽度逐渐增长的效果，当宽度大于长度，代码就会停止，这种行为的结果符合我们的预期；</p></li><li><p>假如我们再把一个正方形作为参数传入resize方法后，就会看到正方形的宽度和长度都在不断增长，代码会一直运行下去，直至系统产生溢出错误。</p></li><li><p>所以，普通的长方形是适合的，正方形不适合。我们得出结论：在resize方法中，Rectangle类型的参数是不能被Square类型的参数所代替，如果进行了替换就得不到预期结果。因此，Square类和Rectangle类之间的继承关系违反了里氏代换原则，它们之间的继承关系不成立，正方形不是长方形。</p></li></ul><p>如何改进呢？此时我们需要重新设计他们之间的关系。抽象出来一个四边形接口(Quadrilateral)，让Rectangle类和Square类实现Quadrilateral接口</p><p><img src="https://img.jwt1399.top/img/202211091603915.png"></p><h3 id="3-依赖倒转原则（DIP）"><a href="#3-依赖倒转原则（DIP）" class="headerlink" title="3.依赖倒转原则（DIP）"></a>3.依赖倒转原则（DIP）</h3><p>高层模块不应该依赖低层模块，两者都应该依赖其抽象；抽象不应该依赖细节，细节应该依赖抽象。简单的说就是要求对抽象进行编程，不要对实现进行编程，这样就降低了客户与实现模块间的耦合。</p><p>【例】组装电脑</p><p>现要组装一台电脑，需要配件cpu，硬盘，内存条。只有这些配置都有了，计算机才能正常的运行。选择cpu有很多选择，如Intel，AMD等，硬盘可以选择希捷，西数等，内存条可以选择金士顿，海盗船等。</p><img src="https://img.jwt1399.top/img/202211092111320.png" style="zoom:67%;" /><p>可以看到已经组装了一台电脑，但是似乎组装的电脑的cpu只能是Intel的，内存条只能是金士顿的，硬盘只能是希捷的，这对用户肯定是不友好的，用户有了机箱肯定是想按照自己的喜好，选择自己喜欢的配件。</p><p>根据依赖倒转原则进行改进：我们只需要修改Computer类，让Computer类依赖抽象（各个配件的接口），而不是依赖于各个组件具体的实现类。</p><img src="https://img.jwt1399.top/img/202211092111536.png" style="zoom:67%;" /><h3 id="4-接口隔离原则（ISP）"><a href="#4-接口隔离原则（ISP）" class="headerlink" title="4.接口隔离原则（ISP）"></a>4.接口隔离原则（ISP）</h3><p>客户端不应该被迫依赖于它不使用的方法；一个类对另一个类的依赖应该建立在最小的接口上。</p><p>【例】安全门案例</p><p>我们需要创建一个黑马品牌的安全门，该安全门具有防火、防水、防盗的功能。可以将防火，防水，防盗功能提取成一个接口，形成一套规范。类图如下：</p><p><img src="https://img.jwt1399.top/img/202211092115850.png"></p><p>上面的设计我们发现了它存在的问题，黑马品牌的安全门具有防盗，防水，防火的功能。现在如果我们还需要再创建一个传智品牌的安全门，而该安全门只具有防盗、防水功能呢？很显然如果实现SafetyDoor接口就违背了接口隔离原则，那么我们如何进行修改呢？</p><p><img src="https://img.jwt1399.top/img/202211092115885.png"></p><h3 id="5-迪米特法则（LOD）"><a href="#5-迪米特法则（LOD）" class="headerlink" title="5.迪米特法则（LOD）"></a>5.迪米特法则（LOD）</h3><p>迪米特法则又叫最少知识原则。只和你的直接朋友交谈，不跟“陌生人”说话。</p><p>如果两个软件实体无须直接通信，那么就不应当发生直接的相互调用，可以通过第三方转发该调用。其目的是降低类之间的耦合度，提高模块的相对独立性。</p><p>【例】明星与经纪人的关系实例</p><p>明星由于全身心投入艺术，所以许多日常事务由经纪人负责处理，如和粉丝的见面会，和媒体公司的业务洽淡等。这里的经纪人是明星的朋友，而粉丝和媒体公司是陌生人，所以适合使用迪米特法则。</p><img src="https://img.jwt1399.top/img/202211092126268.png" style="zoom:67%;" /><h3 id="6-合成复用原则（CARP）"><a href="#6-合成复用原则（CARP）" class="headerlink" title="6.合成复用原则（CARP）"></a>6.合成复用原则（CARP）</h3><p>合成复用原则是指：尽量先使用组合或者聚合等关联关系来实现，其次才考虑使用继承关系来实现。</p><p>【例】汽车分类管理程序</p><p>汽车按“动力源”划分可分为汽油汽车、电动汽车等；按“颜色”划分可分为白色汽车、黑色汽车和红色汽车等。如果同时考虑这两种分类，其组合就很多。</p><img src="https://img.jwt1399.top/img/202211092125045.png" style="zoom:67%;" /><p>从上面类图我们可以看到使用继承复用产生了很多子类，如果现在又有新的动力源或者新的颜色的话，就需要再定义新的类。我们试着将继承复用改为聚合复用看一下。</p><p><img src="https://img.jwt1399.top/img/202211092125707.png"></p><h1 id="①创建型模式-5种"><a href="#①创建型模式-5种" class="headerlink" title="①创建型模式-5种"></a>①创建型模式-5种</h1><blockquote><p>创建型模式的主要关注点是“怎样创建对象？”，它的主要特点是“将对象的创建与使用分离”。</p><p>这样可以降低系统的耦合度，使用者不需要关注对象的创建细节。</p></blockquote><h2 id="❶单例模式"><a href="#❶单例模式" class="headerlink" title="❶单例模式"></a>❶单例模式</h2><p>单例模式（Singleton Pattern）提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类，该类负责<strong>创建自己的对象</strong>，同时确保只有<strong>单个对象被创建</strong>。这个类<strong>提供了一种访问其唯一的对象的方式</strong>，可以直接访问，不需要实例化该类的对象。</p><p>单例设计模式分为两种：</p><ul><li><p>饿汉式：类加载就会导致该单实例对象被创建</p></li><li><p>懒汉式：类加载不会导致该单实例对象被创建，而是首次使用该对象时才会创建</p></li></ul><h3 id="1-饿汉式"><a href="#1-饿汉式" class="headerlink" title="1.饿汉式"></a>1.饿汉式</h3><h4 id="方式1（静态变量方式）"><a href="#方式1（静态变量方式）" class="headerlink" title="方式1（静态变量方式）"></a><strong>方式1（静态变量方式）</strong></h4><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Singleton</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">//私有构造方法</span>    <span class="token keyword">private</span> <span class="token function">Singleton</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">//在成员位置创建该类的对象</span>    <span class="token keyword">private</span> <span class="token keyword">static</span> Singleton instance <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Singleton</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">//对外提供静态方法获取该对象</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> Singleton <span class="token function">getInstance</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">return</span> instance<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>该方式在成员位置声明Singleton类型的静态变量，并创建Singleton类的对象instance。instance对象是随着类的加载而创建的。如果该对象足够大的话，而一直没有使用就会造成内存的浪费。</p><h4 id="方式2（静态代码块方式）"><a href="#方式2（静态代码块方式）" class="headerlink" title="方式2（静态代码块方式）"></a><strong>方式2（静态代码块方式）</strong></h4><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Singleton</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">//私有构造方法</span>    <span class="token keyword">private</span> <span class="token function">Singleton</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">//在成员位置创建该类的对象</span>    <span class="token keyword">private</span> <span class="token keyword">static</span> Singleton instance<span class="token punctuation">;</span>    <span class="token keyword">static</span> <span class="token punctuation">{</span>        instance <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Singleton</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">//对外提供静态方法获取该对象</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> Singleton <span class="token function">getInstance</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">return</span> instance<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>该方式在成员位置声明Singleton类型的静态变量，而对象的创建是在静态代码块中，也是对着类的加载而创建。所以和饿汉式的方式1基本上一样，当然该方式也存在内存浪费问题。</p><h4 id="方式3（枚举方式）-推荐"><a href="#方式3（枚举方式）-推荐" class="headerlink" title="方式3（枚举方式）-推荐"></a>方式3（枚举方式）-推荐</h4><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">enum</span> Singleton <span class="token punctuation">{</span>    INSTANCE<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>枚举类实现单例模式是极力推荐的单例实现模式，因为枚举类型是线程安全的，并且只会装载一次，设计者充分的利用了枚举的这个特性来实现单例模式，枚举的写法非常简单，而且枚举类型是所用单例实现中唯一一种不会被破坏的单例实现模式。</p><h3 id="2-懒汉式"><a href="#2-懒汉式" class="headerlink" title="2.懒汉式"></a>2.懒汉式</h3><h4 id="方式1（线程不安全）"><a href="#方式1（线程不安全）" class="headerlink" title="方式1（线程不安全）"></a><strong>方式1（线程不安全）</strong></h4><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Singleton</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">//私有构造方法</span>    <span class="token keyword">private</span> <span class="token function">Singleton</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">//在成员位置创建该类的对象</span>    <span class="token keyword">private</span> <span class="token keyword">static</span> Singleton instance<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">//对外提供静态方法获取该对象</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> Singleton <span class="token function">getInstance</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 保证始终只创建一个对象</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>instance <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">//这里会出现线程安全问题</span>            instance <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Singleton</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> instance<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>该方式在成员位置声明Singleton类型的静态变量，当调用getInstance()方法获取Singleton类的对象的时候才创建Singleton类的对象，这样就实现了懒加载的效果。但是，如果是多线程环境，会出现线程安全问题。</p><h4 id="方式2（线程安全）"><a href="#方式2（线程安全）" class="headerlink" title="方式2（线程安全）"></a><strong>方式2（线程安全）</strong></h4><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Singleton</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">//私有构造方法</span>    <span class="token keyword">private</span> <span class="token function">Singleton</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">//在成员位置创建该类的对象</span>    <span class="token keyword">private</span> <span class="token keyword">static</span> Singleton instance<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">//对外提供静态方法获取该对象</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">synchronized</span> Singleton <span class="token function">getInstance</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>instance <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>            instance <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Singleton</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> instance<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>该方式也实现了懒加载效果，同时又解决了线程安全问题。但是在getInstance()方法上添加了synchronized关键字，导致该方法的执行效果特别低。从上面代码我们可以看出，其实就是在初始化instance的时候才会出现线程安全问题，一旦初始化完成就不存在了。</p><h4 id="方式3（双重检查锁）-推荐"><a href="#方式3（双重检查锁）-推荐" class="headerlink" title="方式3（双重检查锁）-推荐"></a>方式3（双重检查锁）-推荐</h4><p>对于 <code>getInstance()</code> 方法来说，绝大部分的操作都是读操作，读操作是线程安全的，所以我们没必让每个线程必须持有锁才能调用该方法，我们需要调整加锁的时机。由此也产生了一种新的实现模式：双重检查锁模式</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Singleton</span> <span class="token punctuation">{</span>     <span class="token comment" spellcheck="true">//私有构造方法</span>    <span class="token keyword">private</span> <span class="token function">Singleton</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>    <span class="token keyword">private</span> <span class="token keyword">static</span> Singleton instance<span class="token punctuation">;</span>   <span class="token comment" spellcheck="true">//对外提供静态方法获取该对象</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> Singleton <span class="token function">getInstance</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">//第一次判断，如果instance不为null，不进入抢锁阶段，直接返回实例</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>instance <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token keyword">synchronized</span> <span class="token punctuation">(</span>Singleton<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token comment" spellcheck="true">//抢到锁之后再次判断是否为null</span>                <span class="token keyword">if</span><span class="token punctuation">(</span>instance <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>                    instance <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Singleton</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token punctuation">}</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> instance<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>双重检查锁模式是一种非常好的单例实现模式，解决了单例、性能、线程安全问题，上面的双重检测锁模式看上去完美无缺，其实是存在问题，在多线程的情况下，可能会出现空指针问题，出现问题的原因是JVM在实例化对象的时候会进行优化和指令重排序操作。</p><p>要解决双重检查锁模式带来空指针异常的问题，只需要使用 <code>volatile</code> 关键字, <code>volatile</code> 关键字可以保证可见性和有序性。</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Singleton</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">//私有构造方法</span>    <span class="token keyword">private</span> <span class="token function">Singleton</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>    <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">volatile</span> Singleton instance<span class="token punctuation">;</span>   <span class="token comment" spellcheck="true">//对外提供静态方法获取该对象</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> Singleton <span class="token function">getInstance</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">//第一次判断，如果instance不为null，不进入抢锁阶段，直接返回实际</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>instance <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token keyword">synchronized</span> <span class="token punctuation">(</span>Singleton<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token comment" spellcheck="true">//抢到锁之后再次判断是否为空</span>                <span class="token keyword">if</span><span class="token punctuation">(</span>instance <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>                    instance <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Singleton</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token punctuation">}</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> instance<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>添加 <code>volatile</code> 关键字之后的双重检查锁模式是一种比较好的单例实现模式，能够保证在多线程的情况下线程安全也不会有性能问题。</p><h4 id="方式4（静态内部类）-推荐"><a href="#方式4（静态内部类）-推荐" class="headerlink" title="方式4（静态内部类）-推荐"></a>方式4（静态内部类）-推荐</h4><p>静态内部类单例模式中实例由内部类创建，由于 JVM 在加载外部类的过程中，是不会加载静态内部类的， 只有内部类的属性&#x2F;方法被调用时才会被加载, 并初始化其静态属性。静态属性由于被 <code>static</code> 修饰，保证只被实例化一次，并且严格保证实例化顺序。</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Singleton</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">//私有构造方法</span>    <span class="token keyword">private</span> <span class="token function">Singleton</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>    <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">SingletonHolder</span> <span class="token punctuation">{</span>        <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> Singleton INSTANCE <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Singleton</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">//对外提供静态方法获取该对象</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> Singleton <span class="token function">getInstance</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">return</span> SingletonHolder<span class="token punctuation">.</span>INSTANCE<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>第一次加载Singleton类时不会去初始化INSTANCE，只有第一次调用getInstance，虚拟机加载SingletonHolder，并初始化INSTANCE，这样不仅能确保线程安全，也能保证 Singleton 类的唯一性。</p><p>静态内部类单例模式是一种优秀的单例模式，是开源项目中比较常用的一种单例模式。在没有加任何锁的情况下，保证了多线程下的安全，并且没有任何性能影响和空间的浪费。</p><h2 id="❷工厂模式"><a href="#❷工厂模式" class="headerlink" title="❷工厂模式"></a>❷工厂模式</h2><blockquote><p>需求：设计一个咖啡店点餐系统。  设计一个咖啡类（Coffee），并定义其两个子类（美式咖啡【AmericanCoffee】和拿铁咖啡【LatteCoffee】）；再设计一个咖啡店类（CoffeeStore），咖啡店具有点咖啡的功能。</p></blockquote><p><img src="https://img.jwt1399.top/img/202211101706221.png"></p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Client</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">//1,创建咖啡店类</span>        CoffeeStore store <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">CoffeeStore</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">//2,点咖啡</span>        Coffee coffee <span class="token operator">=</span> store<span class="token punctuation">.</span><span class="token function">orderCoffee</span><span class="token punctuation">(</span><span class="token string">"american"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>coffee<span class="token punctuation">.</span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="1-简单工厂模式"><a href="#1-简单工厂模式" class="headerlink" title="1.简单工厂模式"></a>1.简单工厂模式</h3><p>简单工厂包含如下角色：</p><ul><li>抽象产品 ：定义了产品的规范，描述了产品的主要特性和功能。</li><li>具体产品 ：实现或者继承抽象产品的子类</li><li>具体工厂 ：提供了创建产品的方法，调用者通过该方法来获取产品。</li></ul><p>现在使用简单工厂对上面案例进行改进，类图如下：</p><p><img src="https://img.jwt1399.top/img/202211101706850.png"></p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">SimpleCoffeeFactory</span> <span class="token punctuation">{</span>      <span class="token keyword">public</span> Coffee <span class="token function">createCoffee</span><span class="token punctuation">(</span>String type<span class="token punctuation">)</span> <span class="token punctuation">{</span>        Coffee coffee <span class="token operator">=</span> null<span class="token punctuation">;</span>        <span class="token keyword">if</span><span class="token punctuation">(</span><span class="token string">"americano"</span><span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span>type<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            coffee <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">AmericanoCoffee</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span><span class="token punctuation">(</span><span class="token string">"latte"</span><span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span>type<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            coffee <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">LatteCoffee</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> coffee<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>优点：如果要添加新产品直接修改工厂类，而不需要在原代码中修改，这就降低了客户代码修改的可能性，更易扩展。封装了创建对象的过程，可以通过参数直接获取对象。把对象的创建和业务逻辑层分开，这样以后就避免了修改客户代码。</p><p>工厂处理创建对象的细节：</p><p>一旦有了SimpleCoffeeFactory，CoffeeStore类中的orderCoffee()就变成此对象的客户，后期如果需要Coffee对象直接从工厂中获取即可。这样也就解除了CoffeeStore和Coffee实现类的耦合，但同时又产生了新的耦合，CoffeeStore对象和SimpleCoffeeFactory工厂对象的耦合，工厂对象和商品对象的耦合。后期如果再加新品种的咖啡，我们势必要需求修改SimpleCoffeeFactory的代码，违反了开闭原则。</p><p><strong>静态工厂</strong></p><p>在开发中也有一部分人将工厂类中的创建对象的功能定义为静态的，这个就是静态工厂模式。</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">SimpleCoffeeFactory</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> Coffee <span class="token function">createCoffee</span><span class="token punctuation">(</span>String type<span class="token punctuation">)</span> <span class="token punctuation">{</span>        Coffee coffee <span class="token operator">=</span> null<span class="token punctuation">;</span>        <span class="token keyword">if</span><span class="token punctuation">(</span><span class="token string">"americano"</span><span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span>type<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            coffee <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">AmericanoCoffee</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span><span class="token punctuation">(</span><span class="token string">"latte"</span><span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span>type<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            coffee <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">LatteCoffee</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> coffe<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="2-工厂方法模式"><a href="#2-工厂方法模式" class="headerlink" title="2.工厂方法模式"></a>2.工厂方法模式</h3><p>针对上例中的缺点，使用工厂方法模式就可以完美的解决，完全遵循开闭原则。定义一个用于创建对象的接口，让子类决定实例化哪个产品类对象。工厂方法使一个产品类的实例化延迟到其工厂的子类。</p><p>工厂方法模式的主要角色：</p><ul><li>抽象工厂：提供了创建产品的接口，调用者通过它访问具体工厂的工厂方法来创建产品。</li><li>具体工厂：主要是实现抽象工厂中的抽象方法，完成具体产品的创建。</li><li>抽象产品：定义了产品的规范，描述了产品的主要特性和功能。</li><li>具体产品：实现了抽象产品角色所定义的接口，由具体工厂来创建，它同具体工厂之间一一对应。</li></ul><p><img src="https://img.jwt1399.top/img/202211102132846.png"></p><p>抽象工厂：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">CoffeeFactory</span> <span class="token punctuation">{</span>    Coffee <span class="token function">createCoffee</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>具体工厂：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">LatteCoffeeFactory</span> <span class="token keyword">implements</span> <span class="token class-name">CoffeeFactory</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> Coffee <span class="token function">createCoffee</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">LatteCoffee</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">AmericanCoffeeFactory</span> <span class="token keyword">implements</span> <span class="token class-name">CoffeeFactory</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> Coffee <span class="token function">createCoffee</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">AmericanCoffee</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>咖啡店类：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">CoffeeStore</span> <span class="token punctuation">{</span>    <span class="token keyword">private</span> CoffeeFactory factory<span class="token punctuation">;</span>    <span class="token keyword">public</span> <span class="token function">CoffeeStore</span><span class="token punctuation">(</span>CoffeeFactory factory<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">this</span><span class="token punctuation">.</span>factory <span class="token operator">=</span> factory<span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">public</span> Coffee <span class="token function">orderCoffee</span><span class="token punctuation">(</span>String type<span class="token punctuation">)</span> <span class="token punctuation">{</span>        Coffee coffee <span class="token operator">=</span> factory<span class="token punctuation">.</span><span class="token function">createCoffee</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        coffee<span class="token punctuation">.</span><span class="token function">addMilk</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        coffee<span class="token punctuation">.</span><span class="token function">addsugar</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> coffee<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>优点：</strong></p><ul><li>用户只需要知道具体工厂的名称就可得到所要的产品，无须知道产品的具体创建过程；</li><li>在系统增加新产品时只需要添加具体产品类和对应的具体工厂类，无须对原工厂进行修改，满足开闭原则；</li></ul><p><strong>缺点：</strong></p><ul><li>每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类，这增加了系统的复杂度。</li></ul><h3 id="3-模式扩展"><a href="#3-模式扩展" class="headerlink" title="3.模式扩展"></a>3.模式扩展</h3><p>可以通过<strong>工厂模式+配置文件</strong>的方式解除工厂对象和产品对象的耦合。在工厂类中加载配置文件中的全类名，并创建对象进行存储，客户端如果需要对象，直接进行获取即可。</p><p>第一步：定义配置文件</p><p>为了演示方便，我们使用properties文件作为配置文件，名称为bean.properties</p><pre class="line-numbers language-properties"><code class="language-properties"><span class="token attr-name">american</span><span class="token punctuation">=</span><span class="token attr-value">com.itheima.pattern.factory.config_factory.AmericanCoffee</span><span class="token attr-name">latte</span><span class="token punctuation">=</span><span class="token attr-value">com.itheima.pattern.factory.config_factory.LatteCoffee</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>第二步：改进工厂类</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">CoffeeFactory</span> <span class="token punctuation">{</span>    <span class="token keyword">private</span> <span class="token keyword">static</span> Map<span class="token operator">&lt;</span>String<span class="token punctuation">,</span>Coffee<span class="token operator">></span> map <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">HashMap</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">static</span> <span class="token punctuation">{</span>        Properties p <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Properties</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        InputStream is <span class="token operator">=</span> CoffeeFactory<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">.</span><span class="token function">getClassLoader</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getResourceAsStream</span><span class="token punctuation">(</span><span class="token string">"bean.properties"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">try</span> <span class="token punctuation">{</span>            p<span class="token punctuation">.</span><span class="token function">load</span><span class="token punctuation">(</span>is<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">//遍历Properties集合对象</span>            Set<span class="token operator">&lt;</span>Object<span class="token operator">></span> keys <span class="token operator">=</span> p<span class="token punctuation">.</span><span class="token function">keySet</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">for</span> <span class="token punctuation">(</span>Object key <span class="token operator">:</span> keys<span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token comment" spellcheck="true">//根据键获取值（全类名）</span>                String className <span class="token operator">=</span> p<span class="token punctuation">.</span><span class="token function">getProperty</span><span class="token punctuation">(</span><span class="token punctuation">(</span>String<span class="token punctuation">)</span> key<span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token comment" spellcheck="true">//获取字节码对象</span>                Class <span class="token class-name">clazz</span> <span class="token operator">=</span> Class<span class="token punctuation">.</span><span class="token function">forName</span><span class="token punctuation">(</span>className<span class="token punctuation">)</span><span class="token punctuation">;</span>                Coffee obj <span class="token operator">=</span> <span class="token punctuation">(</span>Coffee<span class="token punctuation">)</span> clazz<span class="token punctuation">.</span><span class="token function">newInstance</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                map<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token punctuation">(</span>String<span class="token punctuation">)</span>key<span class="token punctuation">,</span>obj<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Exception</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span>            e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> Coffee <span class="token function">createCoffee</span><span class="token punctuation">(</span>String name<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">return</span> map<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>name<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>静态成员变量用来存储创建的对象（键存储的是名称，值存储的是对应的对象），而读取配置文件以及创建对象写在静态代码块中，目的就是只需要执行一次。</p><blockquote><p>1,DateForamt类中的getInstance()方法使用的是工厂模式；</p><p>2,Calendar类中的getInstance()方法使用的是工厂模式；</p><p>3,Collection.iterator方法使用的是工厂模式；</p></blockquote><h2 id="❸抽象工厂模式"><a href="#❸抽象工厂模式" class="headerlink" title="❸抽象工厂模式"></a>❸抽象工厂模式</h2><p>抽象工厂模式是工厂方法模式的升级版本，工厂方法模式只生产一个种类的产品，而抽象工厂模式可生产多个种类的产品。</p><p>抽象工厂模式是一种为访问类提供一个创建一组相关或相互依赖对象的接口，且访问类无须指定所要产品的具体类就能得到同族的不同等级的产品的模式结构。</p><p>本节要介绍的抽象工厂模式将考虑多等级产品的生产，将同一个具体工厂所生产的位于不同等级的一组产品称为一个产品族，下图所示横轴是产品等级，也就是同一类产品；纵轴是产品族，也就是同一品牌的产品，同一品牌的产品产自同一个工厂。</p><img src="https://img.jwt1399.top/img/202211102132748.png" style="zoom:67%;" /><p>抽象工厂模式的主要角色如下：</p><ul><li>抽象工厂：提供了创建产品的接口，它包含多个创建产品的方法，可以创建多个不同等级的产品。</li><li>具体工厂：主要是实现抽象工厂中的多个抽象方法，完成具体产品的创建。</li><li>抽象产品：定义了产品的规范，描述了产品的主要特性和功能，抽象工厂模式有多个抽象产品。</li><li>具体产品：实现了抽象产品角色所定义的接口，由具体工厂来创建，它 同具体工厂之间是多对一的关系。</li></ul><p>现咖啡店业务发生改变，不仅要生产咖啡还要生产甜点，如提拉米苏、抹茶慕斯等，要是按照工厂方法模式，需要定义提拉米苏类、抹茶慕斯类、提拉米苏工厂、抹茶慕斯工厂、甜点工厂类，很容易发生类爆炸情况。</p><p>其中拿铁咖啡、美式咖啡是一个产品等级，都是咖啡；提拉米苏、抹茶慕斯也是一个产品等级；</p><p>拿铁和提拉米苏是同一产品族（都属于意大利风味），美式和抹茶慕斯是同一产品族（都属于美式风味）。</p><p>所以这个案例可以使用抽象工厂模式实现。</p><p><img src="https://img.jwt1399.top/img/202211102132449.png"></p><p>抽象工厂：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">DessertFactory</span> <span class="token punctuation">{</span>    Coffee <span class="token function">createCoffee</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    Dessert <span class="token function">createDessert</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>具体工厂：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">//美式甜点工厂</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">AmericanDessertFactory</span> <span class="token keyword">implements</span> <span class="token class-name">DessertFactory</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> Coffee <span class="token function">createCoffee</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">AmericanCoffee</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">public</span> Dessert <span class="token function">createDessert</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">MatchaMousse</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">//意大利风味甜点工厂</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ItalyDessertFactory</span> <span class="token keyword">implements</span> <span class="token class-name">DessertFactory</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> Coffee <span class="token function">createCoffee</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">LatteCoffee</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">public</span> Dessert <span class="token function">createDessert</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">Tiramisu</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>如果要加同一个产品族的话，只需要再加一个对应的工厂类即可，不需要修改其他的类。</p><p><strong>优点：</strong></p><p>当一个产品族中的多个对象被设计成一起工作时，它能保证客户端始终只使用同一个产品族中的对象。</p><p><strong>缺点：</strong></p><p>当产品族中需要增加一个新的产品时，所有的工厂类都需要进行修改。</p><p><strong>使用场景：</strong></p><ul><li><p>当需要创建的对象是一系列相互关联或相互依赖的产品族时，如电器工厂中的电视机、洗衣机、空调等。</p></li><li><p>系统中有多个产品族，但每次只使用其中的某一族产品。如有人只喜欢穿某一个品牌的衣服和鞋。</p></li><li><p>系统中提供了产品的类库，且所有产品的接口相同，客户端不依赖产品实例的创建细节和内部结构。</p></li></ul><h2 id="❹原型模式"><a href="#❹原型模式" class="headerlink" title="❹原型模式"></a>❹原型模式</h2><p>用一个已经创建的实例作为原型，通过复制该原型对象来创建一个和原型对象相同的新对象。</p><p>原型模式包含如下角色：</p><ul><li>抽象原型类：规定了具体原型对象必须实现的的 clone() 方法。</li><li>具体原型类：实现抽象原型类的 clone() 方法，它是可被复制的对象。</li><li>访问类：使用具体原型类中的 clone() 方法来复制新的对象。</li></ul><p><img src="https://img.jwt1399.top/img/202211102133633.png"></p><p>原型模式的克隆分为浅克隆和深克隆。</p><ul><li><p>浅克隆：创建一个新对象，新对象的属性和原来对象完全相同，对于非基本类型属性，仍指向原有属性所指向的对象的内存地址。</p></li><li><p>深克隆：创建一个新对象，属性中引用的其他对象也会被克隆，不再指向原有对象地址。</p></li></ul><h3 id="1-浅克隆"><a href="#1-浅克隆" class="headerlink" title="1.浅克隆"></a>1.浅克隆</h3><p>Java中的Object类中提供了 <code>clone()</code> 方法来实现浅克隆。 Cloneable 接口是上面的类图中的抽象原型类，而实现了Cloneable接口的实现类就是具体的原型类。代码如下：</p><p><strong>Realizetype（具体原型类）：</strong></p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Realizetype</span> <span class="token keyword">implements</span> <span class="token class-name">Cloneable</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token function">Realizetype</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"具体的原型对象创建完成！"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">protected</span> Realizetype <span class="token function">clone</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">throws</span> CloneNotSupportedException <span class="token punctuation">{</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"具体原型复制成功！"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> <span class="token punctuation">(</span>Realizetype<span class="token punctuation">)</span> <span class="token keyword">super</span><span class="token punctuation">.</span><span class="token function">clone</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>PrototypeTest（测试访问类）：</strong></p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">PrototypeTest</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token keyword">throws</span> CloneNotSupportedException <span class="token punctuation">{</span>        Realizetype r1 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Realizetype</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        Realizetype r2 <span class="token operator">=</span> r1<span class="token punctuation">.</span><span class="token function">clone</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"对象r1和r2是同一个对象？"</span> <span class="token operator">+</span> <span class="token punctuation">(</span>r1 <span class="token operator">==</span> r2<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">/***具体的原型对象创建完成！*具体原型复制成功！*对象r1和r2是同一个对象？false*/</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>使用场景</strong></p><ul><li>对象的创建非常复杂，可以使用原型模式快捷的创建对象。</li><li>性能和安全要求比较高。</li></ul><p><strong>案例：用原型模式生成“三好学生”奖状</strong></p><p>同一学校的“三好学生”奖状除了获奖人姓名不同，其他都相同，可以使用原型模式复制多个“三好学生”奖状出来，然后在修改奖状上的名字即可。</p><p><img src="https://img.jwt1399.top/img/202211102139269.png"></p><p>代码如下：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">//奖状类</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Citation</span> <span class="token keyword">implements</span> <span class="token class-name">Cloneable</span> <span class="token punctuation">{</span>    <span class="token keyword">private</span> String name<span class="token punctuation">;</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">setName</span><span class="token punctuation">(</span>String name<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">this</span><span class="token punctuation">.</span>name <span class="token operator">=</span> name<span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">public</span> String <span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>name<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">show</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>name <span class="token operator">+</span> <span class="token string">"同学：在2022学年第一学期中表现优秀，被评为三好学生。特发此状！"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> Citation <span class="token function">clone</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">throws</span> CloneNotSupportedException <span class="token punctuation">{</span>        <span class="token keyword">return</span> <span class="token punctuation">(</span>Citation<span class="token punctuation">)</span> <span class="token keyword">super</span><span class="token punctuation">.</span><span class="token function">clone</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">//测试访问类</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">CitationTest</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token keyword">throws</span> CloneNotSupportedException <span class="token punctuation">{</span>        Citation c1 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Citation</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        c1<span class="token punctuation">.</span><span class="token function">setName</span><span class="token punctuation">(</span><span class="token string">"张三"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">//复制奖状</span>        Citation c2 <span class="token operator">=</span> c1<span class="token punctuation">.</span><span class="token function">clone</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">//将奖状的名字修改李四</span>        c2<span class="token punctuation">.</span><span class="token function">setName</span><span class="token punctuation">(</span><span class="token string">"李四"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        c1<span class="token punctuation">.</span><span class="token function">show</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        c2<span class="token punctuation">.</span><span class="token function">show</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">/***张三同学：在2020学年第一学期中表现优秀，被评为三好学生。特发此状！*李四同学：在2020学年第一学期中表现优秀，被评为三好学生。特发此状！*/</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="2-深克隆"><a href="#2-深克隆" class="headerlink" title="2.深克隆"></a>2.深克隆</h3><p>将上面的“三好学生”奖状的案例中Citation类的name属性修改为Student类型的属性。代码如下：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">//奖状类</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Citation</span> <span class="token keyword">implements</span> <span class="token class-name">Cloneable</span> <span class="token punctuation">{</span>    <span class="token keyword">private</span> Student stu<span class="token punctuation">;</span>    <span class="token keyword">public</span> Student <span class="token function">getStu</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">return</span> stu<span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">setStu</span><span class="token punctuation">(</span>Student stu<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">this</span><span class="token punctuation">.</span>stu <span class="token operator">=</span> stu<span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">void</span> <span class="token function">show</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>stu<span class="token punctuation">.</span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">"同学：在2020学年第一学期中表现优秀，被评为三好学生。特发此状！"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> Citation <span class="token function">clone</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">throws</span> CloneNotSupportedException <span class="token punctuation">{</span>        <span class="token keyword">return</span> <span class="token punctuation">(</span>Citation<span class="token punctuation">)</span> <span class="token keyword">super</span><span class="token punctuation">.</span><span class="token function">clone</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">//学生类</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Student</span> <span class="token punctuation">{</span>    <span class="token keyword">private</span> String name<span class="token punctuation">;</span>    <span class="token keyword">private</span> String address<span class="token punctuation">;</span>    <span class="token keyword">public</span> <span class="token function">Student</span><span class="token punctuation">(</span>String name<span class="token punctuation">,</span> String address<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">this</span><span class="token punctuation">.</span>name <span class="token operator">=</span> name<span class="token punctuation">;</span>        <span class="token keyword">this</span><span class="token punctuation">.</span>address <span class="token operator">=</span> address<span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">public</span> <span class="token function">Student</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token punctuation">}</span>    <span class="token keyword">public</span> String <span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">return</span> name<span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">setName</span><span class="token punctuation">(</span>String name<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">this</span><span class="token punctuation">.</span>name <span class="token operator">=</span> name<span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">public</span> String <span class="token function">getAddress</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">return</span> address<span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">setAddress</span><span class="token punctuation">(</span>String address<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">this</span><span class="token punctuation">.</span>address <span class="token operator">=</span> address<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">//测试类</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">CitationTest</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token keyword">throws</span> CloneNotSupportedException <span class="token punctuation">{</span>        Citation c1 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Citation</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        Student stu <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Student</span><span class="token punctuation">(</span><span class="token string">"张三"</span><span class="token punctuation">,</span> <span class="token string">"西安"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        c1<span class="token punctuation">.</span><span class="token function">setStu</span><span class="token punctuation">(</span>stu<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">//复制奖状</span>        Citation c2 <span class="token operator">=</span> c1<span class="token punctuation">.</span><span class="token function">clone</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">//获取c2奖状所属学生对象</span>        Student stu1 <span class="token operator">=</span> c2<span class="token punctuation">.</span><span class="token function">getStu</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        stu1<span class="token punctuation">.</span><span class="token function">setName</span><span class="token punctuation">(</span><span class="token string">"李四"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">//判断stu对象和stu1对象是否是同一个对象</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"stu和stu1是同一个对象？"</span> <span class="token operator">+</span> <span class="token punctuation">(</span>stu <span class="token operator">==</span> stu1<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        c1<span class="token punctuation">.</span><span class="token function">show</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        c2<span class="token punctuation">.</span><span class="token function">show</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">/***stu和stu1是同一个对象？true*李四同学：在2020学年第一学期中表现优秀，被评为三好学生。特发此状！*李四同学：在2020学年第一学期中表现优秀，被评为三好学生。特发此状！*/</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>stu对象和stu1对象是同一个对象，就会产生将stu1对象中name属性值改为“李四”，两个Citation（奖状）对象中显示的都是李四。这就是浅克隆的效果，对具体原型类（Citation）中的引用类型的属性进行引用的复制。这种情况需要使用深克隆，而进行深克隆需要使用对象流。代码如下：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">CitationTest1</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token keyword">throws</span> Exception <span class="token punctuation">{</span>        Citation c1 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Citation</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        Student stu <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Student</span><span class="token punctuation">(</span><span class="token string">"张三"</span><span class="token punctuation">,</span> <span class="token string">"西安"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        c1<span class="token punctuation">.</span><span class="token function">setStu</span><span class="token punctuation">(</span>stu<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">//创建对象输出流对象</span>        ObjectOutputStream oos <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ObjectOutputStream</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">FileOutputStream</span><span class="token punctuation">(</span><span class="token string">"C:\\Users\\Think\\Desktop\\b.txt"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">//将c1对象写出到文件中</span>        oos<span class="token punctuation">.</span><span class="token function">writeObject</span><span class="token punctuation">(</span>c1<span class="token punctuation">)</span><span class="token punctuation">;</span>        oos<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">//创建对象出入流对象</span>        ObjectInputStream ois <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ObjectInputStream</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">FileInputStream</span><span class="token punctuation">(</span><span class="token string">"C:\\Users\\Think\\Desktop\\b.txt"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">//读取对象</span>        Citation c2 <span class="token operator">=</span> <span class="token punctuation">(</span>Citation<span class="token punctuation">)</span> ois<span class="token punctuation">.</span><span class="token function">readObject</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">//获取c2奖状所属学生对象</span>        Student stu1 <span class="token operator">=</span> c2<span class="token punctuation">.</span><span class="token function">getStu</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        stu1<span class="token punctuation">.</span><span class="token function">setName</span><span class="token punctuation">(</span><span class="token string">"李四"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">//判断stu对象和stu1对象是否是同一个对象</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"stu和stu1是同一个对象？"</span> <span class="token operator">+</span> <span class="token punctuation">(</span>stu <span class="token operator">==</span> stu1<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        c1<span class="token punctuation">.</span><span class="token function">show</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        c2<span class="token punctuation">.</span><span class="token function">show</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">/***stu和stu1是同一个对象？false*张三同学：在2020学年第一学期中表现优秀，被评为三好学生。特发此状！*李四同学：在2020学年第一学期中表现优秀，被评为三好学生。特发此状！*/</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><blockquote><p>注意：Citation类和Student类必须实现Serializable接口，否则会抛NotSerializableException异常。</p></blockquote><h2 id="❺建造者模式"><a href="#❺建造者模式" class="headerlink" title="❺建造者模式"></a>❺建造者模式</h2><h3 id="1-简述"><a href="#1-简述" class="headerlink" title="1.简述"></a>1.简述</h3><p>将一个复杂对象的构建与表示分离，使得同样的构建过程可以创建不同的表示。这个模式适用于：某个对象的构建过程复杂的情况。</p><ul><li>分离了部件的构造(由Builder来负责)和装配(由Director负责)。 从而可以构造出复杂的对象。</li><li>由于实现了构建和装配的解耦。不同的构建器，相同的装配，也可以做出不同的对象；相同的构建器，不同的装配顺序也可以做出不同的对象。也就是实现了构建算法、装配算法的解耦，实现了更好的复用。</li><li>建造者模式可以将部件和其组装过程分开，一步一步创建一个复杂的对象。用户只需要指定复杂对象的类型就可以得到该对象，而无须知道其内部的具体构造细节。</li></ul><p>建造者模式包含如下角色：</p><ul><li><p>抽象建造者类（Builder）：这个接口规定要实现复杂对象的那些部分的创建，并不涉及具体部件对象的创建。</p></li><li><p>具体建造者类（ConcreteBuilder）：实现 Builder 接口，完成复杂产品的各个部件的具体创建方法。在构造过程完成后，提供产品的实例。 </p></li><li><p>产品类（Product）：要创建的复杂对象。</p></li><li><p>指挥者类（Director）：调用具体建造者来创建复杂对象的各个部分，在指导者中不涉及具体产品的信息，只负责保证对象各部分完整创建或按某种顺序创建。</p></li></ul><p><img src="https://img.jwt1399.top/img/202211112144442.png"></p><h3 id="2-实例"><a href="#2-实例" class="headerlink" title="2.实例"></a>2.实例</h3><p><strong>【例】创建共享单车</strong></p><p>生产自行车是一个复杂的过程，它包含了车架，车座等组件的生产。而车架又有碳纤维，铝合金等材质的，车座有橡胶，真皮等材质。对于自行车的生产就可以使用建造者模式。</p><p>这里Bike是产品，包含车架，车座等组件；Builder是抽象建造者，MobikeBuilder和OfoBuilder是具体的建造者；Director是指挥者。类图如下：</p><p><img src="/../images/Java-%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/image-20221111214538601.png"></p><p>具体的代码如下：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">//自行车类</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Bike</span> <span class="token punctuation">{</span>    <span class="token keyword">private</span> String frame<span class="token punctuation">;</span>    <span class="token keyword">private</span> String seat<span class="token punctuation">;</span>    <span class="token keyword">public</span> String <span class="token function">getFrame</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">return</span> frame<span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">setFrame</span><span class="token punctuation">(</span>String frame<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">this</span><span class="token punctuation">.</span>frame <span class="token operator">=</span> frame<span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">public</span> String <span class="token function">getSeat</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">return</span> seat<span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">setSeat</span><span class="token punctuation">(</span>String seat<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">this</span><span class="token punctuation">.</span>seat <span class="token operator">=</span> seat<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">// 抽象 builder 类</span><span class="token keyword">public</span> <span class="token keyword">abstract</span> <span class="token keyword">class</span> <span class="token class-name">Builder</span> <span class="token punctuation">{</span>    <span class="token keyword">protected</span> Bike mBike <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Bike</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">public</span> <span class="token keyword">abstract</span> <span class="token keyword">void</span> <span class="token function">buildFrame</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">public</span> <span class="token keyword">abstract</span> <span class="token keyword">void</span> <span class="token function">buildSeat</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">public</span> <span class="token keyword">abstract</span> Bike <span class="token function">createBike</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">//摩拜单车Builder类</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">MobikeBuilder</span> <span class="token keyword">extends</span> <span class="token class-name">Builder</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">buildFrame</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        mBike<span class="token punctuation">.</span><span class="token function">setFrame</span><span class="token punctuation">(</span><span class="token string">"铝合金车架"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">buildSeat</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        mBike<span class="token punctuation">.</span><span class="token function">setSeat</span><span class="token punctuation">(</span><span class="token string">"真皮车座"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> Bike <span class="token function">createBike</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">return</span> mBike<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">//ofo单车Builder类</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">OfoBuilder</span> <span class="token keyword">extends</span> <span class="token class-name">Builder</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">buildFrame</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        mBike<span class="token punctuation">.</span><span class="token function">setFrame</span><span class="token punctuation">(</span><span class="token string">"碳纤维车架"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">buildSeat</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        mBike<span class="token punctuation">.</span><span class="token function">setSeat</span><span class="token punctuation">(</span><span class="token string">"橡胶车座"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> Bike <span class="token function">createBike</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">return</span> mBike<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">//指挥者类</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Director</span> <span class="token punctuation">{</span>    <span class="token keyword">private</span> Builder mBuilder<span class="token punctuation">;</span>    <span class="token keyword">public</span> <span class="token function">Director</span><span class="token punctuation">(</span>Builder builder<span class="token punctuation">)</span> <span class="token punctuation">{</span>        mBuilder <span class="token operator">=</span> builder<span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">public</span> Bike <span class="token function">construct</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        mBuilder<span class="token punctuation">.</span><span class="token function">buildFrame</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        mBuilder<span class="token punctuation">.</span><span class="token function">buildSeat</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> mBuilder<span class="token punctuation">.</span><span class="token function">createBike</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">//测试类</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Client</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token function">showBike</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">OfoBuilder</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token function">showBike</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">MobikeBuilder</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">showBike</span><span class="token punctuation">(</span>Builder builder<span class="token punctuation">)</span> <span class="token punctuation">{</span>        Director director <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Director</span><span class="token punctuation">(</span>builder<span class="token punctuation">)</span><span class="token punctuation">;</span>        Bike bike <span class="token operator">=</span> director<span class="token punctuation">.</span><span class="token function">construct</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>bike<span class="token punctuation">.</span><span class="token function">getFrame</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>bike<span class="token punctuation">.</span><span class="token function">getSeat</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>注意：</strong>上面示例是 Builder模式的常规用法，指挥者类 Director 在建造者模式中具有很重要的作用，它用于指导具体构建者如何构建产品，控制调用先后次序，并向调用者返回完整的产品类，但是有些情况下需要简化系统结构，可以把指挥者类和抽象建造者进行结合</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// 抽象 builder 类</span><span class="token keyword">public</span> <span class="token keyword">abstract</span> <span class="token keyword">class</span> <span class="token class-name">Builder</span> <span class="token punctuation">{</span>    <span class="token keyword">protected</span> Bike mBike <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Bike</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">public</span> <span class="token keyword">abstract</span> <span class="token keyword">void</span> <span class="token function">buildFrame</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">public</span> <span class="token keyword">abstract</span> <span class="token keyword">void</span> <span class="token function">buildSeat</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">public</span> <span class="token keyword">abstract</span> Bike <span class="token function">createBike</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">public</span> Bike <span class="token function">construct</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">buildFrame</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">BuildSeat</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">createBike</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>说明：</strong>这样做确实简化了系统结构，但同时也加重了抽象建造者类的职责，也不是太符合单一职责原则，如果construct() 过于复杂，建议还是封装到 Director 中。</p><p><strong>优点：</strong></p><ul><li>建造者模式的封装性很好。使用建造者模式可以有效的封装变化，在使用建造者模式的场景中，一般产品类和建造者类是比较稳定的，因此，将主要的业务逻辑封装在指挥者类中对整体而言可以取得比较好的稳定性。</li><li>在建造者模式中，客户端不必知道产品内部组成的细节，将产品本身与产品的创建过程解耦，使得相同的创建过程可以创建不同的产品对象。</li><li>可以更加精细地控制产品的创建过程 。将复杂产品的创建步骤分解在不同的方法中，使得创建过程更加清晰，也更方便使用程序来控制创建过程。</li><li>建造者模式很容易进行扩展。如果有新的需求，通过实现一个新的建造者类就可以完成，基本上不用修改之前已经测试通过的代码，因此也就不会对原有功能引入风险。符合开闭原则。</li></ul><p><strong>缺点：</strong></p><p>造者模式所创建的产品一般具有较多的共同点，其组成部分相似，如果产品之间的差异性很大，则不适合使用建造者模式，因此其使用范围受到一定的限制。</p><p><strong>使用场景：</strong></p><p>建造者（Builder）模式创建的是复杂对象，其产品的各个部分经常面临着剧烈的变化，但将它们组合在一起的算法却相对稳定，所以它通常在以下场合使用。</p><ul><li>创建的对象较复杂，由多个部件构成，各部件面临着复杂的变化，但构件间的建造顺序是稳定的。</li><li>创建对象的算法独立于该对象的组成部分以及它们的装配方式，即产品的构建过程和最终的表示是独立的。</li></ul><h3 id="3-模式扩展：链式编程"><a href="#3-模式扩展：链式编程" class="headerlink" title="3.模式扩展：链式编程"></a>3.模式扩展：链式编程</h3><p>建造者模式除了上面的用途外，在开发中还有一个常用的使用方式，就是当一个类构造器需要传入很多参数时，如果创建这个类的实例，代码可读性会非常差，而且很容易引入错误，此时就可以利用建造者模式进行重构。</p><p>重构前代码如下：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Phone</span> <span class="token punctuation">{</span>    <span class="token keyword">private</span> String cpu<span class="token punctuation">;</span>    <span class="token keyword">private</span> String screen<span class="token punctuation">;</span>    <span class="token keyword">private</span> String memory<span class="token punctuation">;</span>    <span class="token keyword">private</span> String mainboard<span class="token punctuation">;</span>    <span class="token keyword">public</span> <span class="token function">Phone</span><span class="token punctuation">(</span>String cpu<span class="token punctuation">,</span> String screen<span class="token punctuation">,</span> String memory<span class="token punctuation">,</span> String mainboard<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">this</span><span class="token punctuation">.</span>cpu <span class="token operator">=</span> cpu<span class="token punctuation">;</span>        <span class="token keyword">this</span><span class="token punctuation">.</span>screen <span class="token operator">=</span> screen<span class="token punctuation">;</span>        <span class="token keyword">this</span><span class="token punctuation">.</span>memory <span class="token operator">=</span> memory<span class="token punctuation">;</span>        <span class="token keyword">this</span><span class="token punctuation">.</span>mainboard <span class="token operator">=</span> mainboard<span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">public</span> String <span class="token function">getCpu</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">return</span> cpu<span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">setCpu</span><span class="token punctuation">(</span>String cpu<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">this</span><span class="token punctuation">.</span>cpu <span class="token operator">=</span> cpu<span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">public</span> String <span class="token function">getScreen</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">return</span> screen<span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">setScreen</span><span class="token punctuation">(</span>String screen<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">this</span><span class="token punctuation">.</span>screen <span class="token operator">=</span> screen<span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">public</span> String <span class="token function">getMemory</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">return</span> memory<span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">setMemory</span><span class="token punctuation">(</span>String memory<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">this</span><span class="token punctuation">.</span>memory <span class="token operator">=</span> memory<span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">public</span> String <span class="token function">getMainboard</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">return</span> mainboard<span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">setMainboard</span><span class="token punctuation">(</span>String mainboard<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">this</span><span class="token punctuation">.</span>mainboard <span class="token operator">=</span> mainboard<span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> String <span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">return</span> <span class="token string">"Phone{"</span> <span class="token operator">+</span>                <span class="token string">"cpu='"</span> <span class="token operator">+</span> cpu <span class="token operator">+</span> <span class="token string">'\''</span> <span class="token operator">+</span>                <span class="token string">", screen='"</span> <span class="token operator">+</span> screen <span class="token operator">+</span> <span class="token string">'\''</span> <span class="token operator">+</span>                <span class="token string">", memory='"</span> <span class="token operator">+</span> memory <span class="token operator">+</span> <span class="token string">'\''</span> <span class="token operator">+</span>                <span class="token string">", mainboard='"</span> <span class="token operator">+</span> mainboard <span class="token operator">+</span> <span class="token string">'\''</span> <span class="token operator">+</span>                <span class="token string">'}'</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Client</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">//构建Phone对象</span>        Phone phone <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Phone</span><span class="token punctuation">(</span><span class="token string">"intel"</span><span class="token punctuation">,</span><span class="token string">"三星屏幕"</span><span class="token punctuation">,</span><span class="token string">"金士顿"</span><span class="token punctuation">,</span><span class="token string">"华硕"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>phone<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>构建Phone对象，传递了四个参数，如果参数更多呢？代码的可读性及使用的成本就是比较高。</p><p>重构后代码：</p><blockquote><p>lombok中@Builder注解可实现类似功能（链式编程）</p></blockquote><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Phone</span> <span class="token punctuation">{</span>    <span class="token keyword">private</span> String cpu<span class="token punctuation">;</span>    <span class="token keyword">private</span> String screen<span class="token punctuation">;</span>    <span class="token keyword">private</span> String memory<span class="token punctuation">;</span>    <span class="token keyword">private</span> String mainboard<span class="token punctuation">;</span>    <span class="token keyword">private</span> <span class="token function">Phone</span><span class="token punctuation">(</span>Builder builder<span class="token punctuation">)</span> <span class="token punctuation">{</span>        cpu <span class="token operator">=</span> builder<span class="token punctuation">.</span>cpu<span class="token punctuation">;</span>        screen <span class="token operator">=</span> builder<span class="token punctuation">.</span>screen<span class="token punctuation">;</span>        memory <span class="token operator">=</span> builder<span class="token punctuation">.</span>memory<span class="token punctuation">;</span>        mainboard <span class="token operator">=</span> builder<span class="token punctuation">.</span>mainboard<span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">class</span> <span class="token class-name">Builder</span> <span class="token punctuation">{</span>        <span class="token keyword">private</span> String cpu<span class="token punctuation">;</span>        <span class="token keyword">private</span> String screen<span class="token punctuation">;</span>        <span class="token keyword">private</span> String memory<span class="token punctuation">;</span>        <span class="token keyword">private</span> String mainboard<span class="token punctuation">;</span>        <span class="token keyword">public</span> <span class="token function">Builder</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>        <span class="token keyword">public</span> Builder <span class="token function">cpu</span><span class="token punctuation">(</span>String val<span class="token punctuation">)</span> <span class="token punctuation">{</span>            cpu <span class="token operator">=</span> val<span class="token punctuation">;</span>            <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">public</span> Builder <span class="token function">screen</span><span class="token punctuation">(</span>String val<span class="token punctuation">)</span> <span class="token punctuation">{</span>            screen <span class="token operator">=</span> val<span class="token punctuation">;</span>            <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">public</span> Builder <span class="token function">memory</span><span class="token punctuation">(</span>String val<span class="token punctuation">)</span> <span class="token punctuation">{</span>            memory <span class="token operator">=</span> val<span class="token punctuation">;</span>            <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">public</span> Builder <span class="token function">mainboard</span><span class="token punctuation">(</span>String val<span class="token punctuation">)</span> <span class="token punctuation">{</span>            mainboard <span class="token operator">=</span> val<span class="token punctuation">;</span>            <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">public</span> Phone <span class="token function">build</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">Phone</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> String <span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">return</span> <span class="token string">"Phone{"</span> <span class="token operator">+</span>                <span class="token string">"cpu='"</span> <span class="token operator">+</span> cpu <span class="token operator">+</span> <span class="token string">'\''</span> <span class="token operator">+</span>                <span class="token string">", screen='"</span> <span class="token operator">+</span> screen <span class="token operator">+</span> <span class="token string">'\''</span> <span class="token operator">+</span>                <span class="token string">", memory='"</span> <span class="token operator">+</span> memory <span class="token operator">+</span> <span class="token string">'\''</span> <span class="token operator">+</span>                <span class="token string">", mainboard='"</span> <span class="token operator">+</span> mainboard <span class="token operator">+</span> <span class="token string">'\''</span> <span class="token operator">+</span>                <span class="token string">'}'</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Client</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>        Phone phone <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Phone<span class="token punctuation">.</span>Builder</span><span class="token punctuation">(</span><span class="token punctuation">)</span>                <span class="token punctuation">.</span><span class="token function">cpu</span><span class="token punctuation">(</span><span class="token string">"intel"</span><span class="token punctuation">)</span>                <span class="token punctuation">.</span><span class="token function">mainboard</span><span class="token punctuation">(</span><span class="token string">"华硕"</span><span class="token punctuation">)</span>                <span class="token punctuation">.</span><span class="token function">memory</span><span class="token punctuation">(</span><span class="token string">"金士顿"</span><span class="token punctuation">)</span>                <span class="token punctuation">.</span><span class="token function">screen</span><span class="token punctuation">(</span><span class="token string">"三星"</span><span class="token punctuation">)</span>                <span class="token punctuation">.</span><span class="token function">build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>phone<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>重构后的代码在使用起来更方便，某种程度上也可以提高开发效率。从软件设计上，对程序员的要求比较高。</p><h2 id="❻创建型模式对比"><a href="#❻创建型模式对比" class="headerlink" title="❻创建型模式对比"></a>❻创建型模式对比</h2><h3 id="1-工厂方法模式VS建造者模式"><a href="#1-工厂方法模式VS建造者模式" class="headerlink" title="1.工厂方法模式VS建造者模式"></a>1.工厂方法模式VS建造者模式</h3><p>工厂方法模式注重的是整体对象的创建方式；而建造者模式注重的是部件构建的过程，意在通过一步一步地精确构造创建出一个复杂的对象。</p><p>我们举个简单例子来说明两者的差异，如要制造一个超人，如果使用工厂方法模式，直接产生出来的就是一个力大无穷、能够飞翔、内裤外穿的超人；而如果使用建造者模式，则需要组装手、头、脚、躯干等部分，然后再把内裤外穿，于是一个超人就诞生了。</p><h3 id="2-抽象工厂模式VS建造者模式"><a href="#2-抽象工厂模式VS建造者模式" class="headerlink" title="2.抽象工厂模式VS建造者模式"></a>2.抽象工厂模式VS建造者模式</h3><p>抽象工厂模式实现对产品家族的创建，一个产品家族是这样的一系列产品：具有不同分类维度的产品组合，采用抽象工厂模式则是不需要关心构建过程，只关心什么产品由什么工厂生产即可。</p><p>建造者模式则是要求按照指定的蓝图建造产品，它的主要目的是通过组装零配件而产生一个新产品。</p><p>如果将抽象工厂模式看成汽车配件生产工厂，生产一个产品族的产品，那么建造者模式就是一个汽车组装工厂，通过对部件的组装可以返回一辆完整的汽车。</p><h1 id="②结构型模式-7种"><a href="#②结构型模式-7种" class="headerlink" title="②结构型模式-7种"></a>②结构型模式-7种</h1><blockquote><p>结构型模式描述如何将类或对象按某种布局组成更大的结构。分为类结构型模式和对象结构型模式，前者采用继承机制来组织接口和类，后者釆用组合或聚合来组合对象。</p><p>由于组合关系或聚合关系比继承关系耦合度低，满足“合成复用原则”，所以对象结构型模式比类结构型模式具有更大的灵活性。</p></blockquote><h2 id="❶代理模式"><a href="#❶代理模式" class="headerlink" title="❶代理模式"></a>❶代理模式</h2><h3 id="0-简述"><a href="#0-简述" class="headerlink" title="0.简述"></a>0.简述</h3><p>由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时，访问对象不适合或者不能直接引用目标对象，代理对象作为访问对象和目标对象之间的中介。</p><p>Java中的代理按照代理类生成时机不同又分为静态代理和动态代理。</p><ul><li>静态代理代理类在<strong>编译期就生成</strong></li><li>动态代理代理类则是在<strong>Java运行时动态生成</strong>。<ul><li>动态代理又有<strong>JDK代理</strong>和<strong>CGLib代理</strong>两种。</li></ul></li></ul><p>代理（Proxy）模式分为三种角色：</p><ul><li>抽象主题（Subject）类： 通过接口或抽象类声明真实主题和代理对象实现的业务方法。</li><li>真实主题（Real Subject）类： 实现了抽象主题中的具体业务，是代理对象所代表的真实对象，是最终要引用的对象。</li><li>代理（Proxy）类 ： 提供了与真实主题相同的接口，其内部含有对真实主题的引用，它可以访问、控制或扩展真实主题的功能。</li></ul><h3 id="1-静态代理"><a href="#1-静态代理" class="headerlink" title="1.静态代理"></a>1.静态代理</h3><p>【例】火车站卖票</p><p>如果要买火车票的话，需要去火车站买票，坐车到火车站，排队等一系列的操作，显然比较麻烦。而火车站在多个地方都有代售点，我们去代售点买票就方便很多了。这个例子其实就是典型的代理模式，火车站是目标对象，代售点是代理对象。类图如下：</p><p><img src="https://img.jwt1399.top/img/202211121627835.png"></p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">//卖票接口</span><span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">SellTickets</span> <span class="token punctuation">{</span>    <span class="token keyword">void</span> <span class="token function">sell</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">//火车站  火车站具有卖票功能，所以需要实现SellTickets接口</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">TrainStation</span> <span class="token keyword">implements</span> <span class="token class-name">SellTickets</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">sell</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"火车站卖票"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">//代售点</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ProxyPoint</span> <span class="token keyword">implements</span> <span class="token class-name">SellTickets</span> <span class="token punctuation">{</span>    <span class="token keyword">private</span> TrainStation station <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">TrainStation</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">sell</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"代理点收取一些服务费用"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        station<span class="token punctuation">.</span><span class="token function">sell</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">//测试类</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Client</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>        ProxyPoint pp <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ProxyPoint</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        pp<span class="token punctuation">.</span><span class="token function">sell</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>从上面代码中可以看出测试类直接访问的是ProxyPoint类对象，也就是说ProxyPoint作为访问对象和目标对象的中介。同时也对sell方法进行了增强（代理点收取一些服务费用）。</p><h3 id="2-JDK动态代理"><a href="#2-JDK动态代理" class="headerlink" title="2.JDK动态代理"></a>2.JDK动态代理</h3><p>Java中提供了一个动态代理类Proxy，Proxy并不是我们上述所说的代理对象的类，而是提供了一个创建代理对象的静态方法（newProxyInstance方法）来获取代理对象。</p><p>代码如下：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">//卖票接口</span><span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">SellTickets</span> <span class="token punctuation">{</span>    <span class="token keyword">void</span> <span class="token function">sell</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">//火车站  火车站具有卖票功能，所以需要实现SellTickets接口</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">TrainStation</span> <span class="token keyword">implements</span> <span class="token class-name">SellTickets</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">sell</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"火车站卖票"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">//代理工厂，用来创建代理对象</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ProxyFactory</span> <span class="token punctuation">{</span>    <span class="token keyword">private</span> TrainStation station <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">TrainStation</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">public</span> SellTickets <span class="token function">getProxyObject</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">//使用Proxy获取代理对象</span>        <span class="token comment" spellcheck="true">/*            newProxyInstance()方法参数说明：                ClassLoader loader ： 类加载器，用于加载代理类，使用真实对象的类加载器即可                Class&lt;?>[] interfaces ： 真实对象所实现的接口，代理模式真实对象和代理对象实现相同的接口                InvocationHandler h ： 代理对象的调用处理程序         */</span>        SellTickets sellTickets <span class="token operator">=</span> <span class="token punctuation">(</span>SellTickets<span class="token punctuation">)</span> Proxy<span class="token punctuation">.</span><span class="token function">newProxyInstance</span><span class="token punctuation">(</span>station<span class="token punctuation">.</span><span class="token function">getClass</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getClassLoader</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>                station<span class="token punctuation">.</span><span class="token function">getClass</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getInterfaces</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>                <span class="token keyword">new</span> <span class="token class-name">InvocationHandler</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                    <span class="token comment" spellcheck="true">/*                        InvocationHandler中invoke方法参数说明：                            proxy ： 代理对象                            method ： 对应于在代理对象上调用的接口方法的 Method 实例                            args ： 代理对象调用接口方法时传递的实际参数                     */</span>                    <span class="token keyword">public</span> Object <span class="token function">invoke</span><span class="token punctuation">(</span>Object proxy<span class="token punctuation">,</span> Method method<span class="token punctuation">,</span> Object<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token keyword">throws</span> Throwable <span class="token punctuation">{</span>                        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"代理点收取一些服务费用(JDK动态代理方式)"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                        <span class="token comment" spellcheck="true">//执行真实对象</span>                        Object result <span class="token operator">=</span> method<span class="token punctuation">.</span><span class="token function">invoke</span><span class="token punctuation">(</span>station<span class="token punctuation">,</span> args<span class="token punctuation">)</span><span class="token punctuation">;</span>                        <span class="token keyword">return</span> result<span class="token punctuation">;</span>                    <span class="token punctuation">}</span>                <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> sellTickets<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">//测试类</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Client</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">//获取代理对象</span>        ProxyFactory factory <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ProxyFactory</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                SellTickets proxyObject <span class="token operator">=</span> factory<span class="token punctuation">.</span><span class="token function">getProxyObject</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        proxyObject<span class="token punctuation">.</span><span class="token function">sell</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>执行流程如下：</p><pre><code>1. 在测试类中通过代理对象调用sell()方法2. 根据多态的特性，执行的是代理类（$Proxy0）中的sell()方法3. 代理类（$Proxy0）中的sell()方法中又调用了InvocationHandler接口的子实现类对象的invoke方法4. invoke方法通过反射执行了真实对象所属类(TrainStation)中的sell()方法</code></pre><h3 id="3-CGLIB动态代理"><a href="#3-CGLIB动态代理" class="headerlink" title="3.CGLIB动态代理"></a>3.CGLIB动态代理</h3><p>如果没有定义SellTickets接口，只定义了TrainStation(火车站类)。很显然JDK代理是无法使用了，因为JDK动态代理要求必须定义接口，对接口进行代理。</p><p>CGLIB是一个功能强大，高性能的代码生成包。它为没有实现接口的类提供代理，为JDK的动态代理提供了很好的补充。CGLIB是第三方提供的包，所以需要引入jar包的坐标：</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>cglib<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>cglib<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>2.2.2<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">//火车站</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">TrainStation</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">sell</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"火车站卖票"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">//代理工厂</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ProxyFactory</span> <span class="token keyword">implements</span> <span class="token class-name">MethodInterceptor</span> <span class="token punctuation">{</span>    <span class="token keyword">private</span> TrainStation target <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">TrainStation</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">public</span> TrainStation <span class="token function">getProxyObject</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">//创建Enhancer对象，类似于JDK动态代理的Proxy类，下一步就是设置几个参数</span>        Enhancer enhancer <span class="token operator">=</span><span class="token keyword">new</span> <span class="token class-name">Enhancer</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">//设置父类的字节码对象</span>        enhancer<span class="token punctuation">.</span><span class="token function">setSuperclass</span><span class="token punctuation">(</span>target<span class="token punctuation">.</span><span class="token function">getClass</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">//设置回调函数</span>        enhancer<span class="token punctuation">.</span><span class="token function">setCallback</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">//创建代理对象</span>        TrainStation obj <span class="token operator">=</span> <span class="token punctuation">(</span>TrainStation<span class="token punctuation">)</span> enhancer<span class="token punctuation">.</span><span class="token function">create</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> obj<span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">/*        intercept方法参数说明：            o ： 代理对象            method ： 真实对象中的方法的Method实例            args ： 实际参数            methodProxy ：代理对象中的方法的method实例     */</span>    <span class="token keyword">public</span> TrainStation <span class="token function">intercept</span><span class="token punctuation">(</span>Object o<span class="token punctuation">,</span> Method method<span class="token punctuation">,</span> Object<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">,</span> MethodProxy methodProxy<span class="token punctuation">)</span> <span class="token keyword">throws</span> Throwable <span class="token punctuation">{</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"代理点收取一些服务费用(CGLIB动态代理方式)"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        TrainStation result <span class="token operator">=</span> <span class="token punctuation">(</span>TrainStation<span class="token punctuation">)</span> methodProxy<span class="token punctuation">.</span><span class="token function">invokeSuper</span><span class="token punctuation">(</span>o<span class="token punctuation">,</span> args<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> result<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">//测试类</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Client</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">//创建代理工厂对象</span>        ProxyFactory factory <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ProxyFactory</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">//获取代理对象</span>        TrainStation proxyObject <span class="token operator">=</span> factory<span class="token punctuation">.</span><span class="token function">getProxyObject</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        proxyObject<span class="token punctuation">.</span><span class="token function">sell</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="4-总结"><a href="#4-总结" class="headerlink" title="4.总结"></a>4.总结</h3><ul><li>jdk代理和CGLIB代理</li></ul><p>使用CGLib实现动态代理，CGLib底层采用ASM字节码生成框架，使用字节码技术生成代理类，在JDK1.6之前比使用Java反射效率要高。唯一需要注意的是，<strong>CGLib不能对声明为final的类或者方法进行代理，因为CGLib原理是动态生成被代理类的子类。</strong></p><p>JDK1.8的时候，JDK代理效率高于CGLib代理。所以如果有接口使用JDK动态代理，如果没有接口使用CGLIB代理。</p><ul><li>动态代理和静态代理</li></ul><p>动态代理与静态代理相比较，最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理（InvocationHandler.invoke）。这样，在接口方法数量比较多的时候，我们可以进行灵活处理，而不需要像静态代理那样每一个方法进行中转。</p><p>如果接口增加一个方法，静态代理模式除了所有实现类需要实现这个方法外，所有代理类也需要实现此方法。增加了代码维护的复杂度。而动态代理不会出现该问题</p><p><strong>优点：</strong></p><ul><li>代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用；</li><li>代理对象可以扩展目标对象的功能；</li><li>代理模式能将客户端与目标对象分离，在一定程度上降低了系统的耦合度；</li></ul><p><strong>缺点：</strong></p><ul><li>增加了系统的复杂度；</li></ul><p><strong>使用场景：</strong></p><ul><li><p>远程（Remote）代理</p><p>本地服务通过网络请求远程服务。为了实现本地到远程的通信，我们需要实现网络通信，处理其中可能的异常。为良好的代码设计和可维护性，我们将网络通信部分隐藏起来，只暴露给本地服务一个接口，通过该接口即可访问远程服务提供的功能，而不必过多关心通信部分的细节。</p></li><li><p>防火墙（Firewall）代理</p><p>当你将浏览器配置成使用代理功能时，防火墙就将你的浏览器的请求转给互联网；当互联网返回响应时，代理服务器再把它转给你的浏览器。</p></li><li><p>保护（Protect or Access）代理</p><p>控制对一个对象的访问，如果需要，可以给不同的用户提供不同级别的使用权限。</p></li></ul><h2 id="❷适配器模式"><a href="#❷适配器模式" class="headerlink" title="❷适配器模式"></a>❷适配器模式</h2><h2 id="❸装饰者模式"><a href="#❸装饰者模式" class="headerlink" title="❸装饰者模式"></a>❸装饰者模式</h2><h2 id="❹桥接模式"><a href="#❹桥接模式" class="headerlink" title="❹桥接模式"></a>❹桥接模式</h2><h2 id="❺外观模式"><a href="#❺外观模式" class="headerlink" title="❺外观模式"></a>❺外观模式</h2><h2 id="❻组合模式"><a href="#❻组合模式" class="headerlink" title="❻组合模式"></a>❻组合模式</h2><h2 id="❼享元模式"><a href="#❼享元模式" class="headerlink" title="❼享元模式"></a>❼享元模式</h2><h1 id="③行为型模式-11种"><a href="#③行为型模式-11种" class="headerlink" title="③行为型模式-11种"></a>③行为型模式-11种</h1><h2 id="模版模式"><a href="#模版模式" class="headerlink" title="模版模式"></a>模版模式</h2><h3 id="1-概述"><a href="#1-概述" class="headerlink" title="1 概述"></a>1 概述</h3><p>在面向对象程序设计过程中，程序员常常会遇到这种情况：设计一个系统时知道了算法所需的关键步骤，而且确定了这些步骤的执行顺序，但某些步骤的具体实现还未知，或者说某些步骤的实现与具体的环境相关。</p><p>例如，去银行办理业务一般要经过以下4个流程：取号、排队、办理具体业务、对银行工作人员进行评分等，其中取号、排队和对银行工作人员进行评分的业务对每个客户是一样的，可以在父类中实现，但是办理具体业务却因人而异，它可能是存款、取款或者转账等，可以延迟到子类中实现。</p><p><strong>模版模式：</strong>定义一个操作中的算法骨架，而将算法的一些步骤延迟到子类中，使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。</p><h3 id="2-结构"><a href="#2-结构" class="headerlink" title="2 结构"></a>2 结构</h3><p>模板方法（Template Method）模式包含以下主要角色：</p><ul><li><p>抽象类（Abstract Class）：负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。</p><ul><li><p>模板方法：定义了算法的骨架，按某种顺序调用其包含的基本方法。</p></li><li><p>基本方法：是实现算法各个步骤的方法，是模板方法的组成部分。基本方法又可以分为三种：</p><ul><li><p>抽象方法(Abstract Method) ：一个抽象方法由抽象类声明、由其具体子类实现。</p></li><li><p>具体方法(Concrete Method) ：一个具体方法由一个抽象类或具体类声明并实现，其子类可以进行覆盖也可以直接继承。</p></li><li><p>钩子方法(Hook Method) ：在抽象类中已经实现，包括用于判断的逻辑方法和需要子类重写的空方法两种。一般钩子方法是用于判断的逻辑方法，这类方法名一般为isXxx，返回值类型为boolean类型。</p></li></ul></li></ul></li><li><p>具体子类（Concrete Class）：实现抽象类中所定义的抽象方法和钩子方法，它们是一个顶级逻辑的组成步骤。</p></li></ul><h3 id="3-案例实现"><a href="#3-案例实现" class="headerlink" title="3 案例实现"></a>3 案例实现</h3><p>【例】炒菜的步骤是固定的，分为倒油、热油、倒蔬菜、倒调料品、翻炒等步骤。现通过模板方法模式来用代码模拟。类图如下：</p><img src="https://img.jwt1399.top/img/202303091613547.png" style="zoom:80%;" /><p>代码如下：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">abstract</span> <span class="token keyword">class</span> <span class="token class-name">AbstractClass</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">//抽象类</span>        <span class="token comment" spellcheck="true">//为防止恶意操作，一般模板方法都加上 final 关键词</span>    <span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token keyword">void</span> <span class="token function">cookProcess</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">//模板方法</span>        <span class="token comment" spellcheck="true">//第一步：倒油</span>        <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">pourOil</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">//第二步：热油</span>        <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">heatOil</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">//第三步：倒蔬菜</span>        <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">pourVegetable</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">//第四步：倒调味料</span>        <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">pourSauce</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">//第五步：翻炒</span>        <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">fry</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">//第一步：倒油是一样的，所以直接实现</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">pourOil</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>         System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"倒油"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">//基本方法-具体方法</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">//第二步：热油是一样的，所以直接实现</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">heatOil</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"热油"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>          <span class="token comment" spellcheck="true">//基本方法-具体方法</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">//第三步：倒蔬菜是不一样的</span>    <span class="token keyword">public</span> <span class="token keyword">abstract</span> <span class="token keyword">void</span> <span class="token function">pourVegetable</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment" spellcheck="true">//基本方法-抽象方法</span>    <span class="token comment" spellcheck="true">//第四步：倒调味料是不一样</span>    <span class="token keyword">public</span> <span class="token keyword">abstract</span> <span class="token keyword">void</span> <span class="token function">pourSauce</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token comment" spellcheck="true">//基本方法-抽象方法</span>    <span class="token comment" spellcheck="true">//第五步：翻炒是一样的，所以直接实现</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">fry</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>                    <span class="token comment" spellcheck="true">//基本方法-具体方法</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"炒啊炒啊炒到熟啊"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ConcreteClass_BaoCai</span> <span class="token keyword">extends</span> <span class="token class-name">AbstractClass</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">//具体子类</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">pourVegetable</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"下锅的蔬菜是包菜"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">pourSauce</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"下锅的酱料是辣椒"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ConcreteClass_CaiXin</span> <span class="token keyword">extends</span> <span class="token class-name">AbstractClass</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">//具体子类</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">pourVegetable</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"下锅的蔬菜是菜心"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">pourSauce</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"下锅的酱料是蒜蓉"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">//测试</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Client</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">//炒手撕包菜</span>        ConcreteClass_BaoCai baoCai <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ConcreteClass_BaoCai</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        baoCai<span class="token punctuation">.</span><span class="token function">cookProcess</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">//炒蒜蓉菜心</span>        ConcreteClass_CaiXin caiXin <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ConcreteClass_CaiXin</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        caiXin<span class="token punctuation">.</span><span class="token function">cookProcess</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><blockquote><p>注意：为防止恶意操作，一般模板方法都加上 final 关键词。</p></blockquote><h3 id="4-优缺点"><a href="#4-优缺点" class="headerlink" title="4 优缺点"></a>4 优缺点</h3><p><strong>优点：</strong></p><ul><li><p>提高代码复用性：将相同部分的代码放在抽象的父类中，而将不同的代码放入不同的子类中。</p></li><li><p>实现了反向控制：通过父类调用其子类的操作，通过对子类的具体实现扩展不同的行为，实现了反向控制 ，并符合“开闭原则”。</p></li></ul><p><strong>缺点：</strong></p><ul><li>对每个不同的实现都需要定义一个子类，这会导致类的个数增加，系统更加庞大，设计也更加抽象。</li><li>父类中的抽象方法由子类实现，子类执行的结果会影响父类的结果，这导致一种反向的控制结构，它提高了代码阅读的难度。</li></ul><h3 id="5-适用场景"><a href="#5-适用场景" class="headerlink" title="5 适用场景"></a>5 适用场景</h3><ul><li>算法的整体步骤很固定，但其中个别部分易变时，这时候可以使用模板方法模式，将容易变的部分抽象出来，供子类实现。</li><li>需要通过子类来决定父类算法中某个步骤是否执行，实现子类对父类的反向控制。</li></ul><h2 id="策略模式"><a href="#策略模式" class="headerlink" title="策略模式"></a>策略模式</h2><p>策略模式是一种行为模式，也是替代大量<code>if else</code>的利器。</p><h3 id="使用场景"><a href="#使用场景" class="headerlink" title="使用场景"></a>使用场景</h3><p>一般是具有同类可替代的行为逻辑算法场景。</p><ul><li>不同类型的交易方式(信用卡、支付宝、微信)</li><li>生成唯一ID策略(UUID、DB自增、DB+Redis、雪花算法、Leaf算法)</li></ul><p>都可以使用策略模式进行行为包装，供给外部使用。</p><p>通过策略设计模式的使用可以把我们方法中的if语句优化掉，大量的if语句使用会让代码难以扩展，也不好维护，同时在后期遇到各种问题也很难维护。在使用这样的设计模式后可以很好的满足隔离性与和扩展性，对于不断新增的需求也非常方便承接。</p><p>案例：<a href="https://mp.weixin.qq.com/s/zOFLtSFVrYEyTuihzwgKYw">实战策略模式「模拟多种营销类型优惠券，折扣金额计算策略场景」 (qq.com)</a></p><h1 id="Sponsor❤️"><a href="#Sponsor❤️" class="headerlink" title="Sponsor❤️"></a>Sponsor❤️</h1><p>您的支持是我不断前进的动力，如果您感觉本文对您有所帮助的话，可以考虑打赏一下本文，用以维持本博客的运营费用，拒绝白嫖，从你我做起！🥰🥰🥰</p><table>  <tbody>     <tr>         <td style="text-align:center;">支付宝</td>         <td style="text-align:center;">微信</td>     </tr>   <tr>    <td style="text-align:center;" ><img width="200" src="https://jwt1399.top/medias/reward/alipay.png"></td>          <td style="text-align:center;"><img width="200" src="https://jwt1399.top/medias/reward/sponor_wechat.png"></td>       </tr></tbody></table>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;h1 id=&quot;⓪设计模式基础&quot;&gt;&lt;a href=&quot;#⓪设计模式基础&quot; class=&quot;headerlink&quot; title=&quot;⓪设计模式基础&quot;&gt;&lt;/a&gt;⓪设计模式基础&lt;/h1&gt;&lt;h2 id=&quot;❶设计模式分类&quot;&gt;&lt;a href=&quot;#❶设计模式分类&quot;</summary>
        
      
    
    
    
    <category term="JavaSE" scheme="https://jwt1399.top/categories/JavaSE/"/>
    
    
    <category term="设计模式" scheme="https://jwt1399.top/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
    
  </entry>
  
  <entry>
    <title>可搜索加密：DSSE方案</title>
    <link href="https://jwt1399.top/posts/62723.html"/>
    <id>https://jwt1399.top/posts/62723.html</id>
    <published>2022-10-31T06:42:29.000Z</published>
    <updated>2024-08-03T02:06:54.292Z</updated>
    
    <content type="html"><![CDATA[<p><strong>论文名称：《Dynamic Searchable Symmetric Encryption》</strong></p><ul><li>作者：Seny Kamara、Charalampos Papamanthou、Tom Roeder </li><li>单位：微软研究院、加州大学伯克利分校</li><li>会议：<a href="https://dl.acm.org/doi/10.1145/2382196.2382298">CCS ‘12: Proceedings of the 2012 ACM conference on Computer and communications</a>【CCF A】</li><li>时间：2012年10月</li></ul><table><thead><tr><th align="center">多关键字</th><th align="center">模糊搜索</th><th align="center">可验证</th></tr></thead><tbody><tr><td align="center">✅</td><td align="center">❌</td><td align="center">❌</td></tr><tr><td align="center"><strong>动态更新</strong></td><td align="center"><strong>安全性</strong></td><td align="center"><strong>复杂度</strong></td></tr><tr><td align="center">✅</td><td align="center">IND-CKA2</td><td align="center">亚线性 $O(#f_w)$</td></tr></tbody></table><h2 id="1、方案简介"><a href="#1、方案简介" class="headerlink" title="1、方案简介"></a>1、方案简介</h2><p>任何实用的SSE方案都应该（至少）满足以下属性：<strong>亚线性搜索时间</strong>、<strong>抵御自适应选择关键字攻击的安全性</strong>、</p><p><strong>紧凑的索引</strong>以及<strong>有效地添加和删除文件的能力</strong>。本文提出了第一个SSE方案来满足上述所有属性。我们方案扩展了</p><p>倒排索引方法，并为SSE的设计引入了新技术。</p><p><strong>我们的贡献</strong>：</p><ol><li>我们提出了<strong>动态SSE的形式化安全定义</strong>。</li><li>我们构造了第一个SSE方案，它是<strong>动态的</strong>，<strong>CKA2-secure</strong> 的，并且实现了<strong>最优的搜索时间</strong>。</li><li>我们描述了基于<a href="https://dl.acm.org/doi/abs/10.5555/2590701.2590705">可搜索的对称加密：改进的定义和高效的结构</a>的倒排索引方法的SSE方案的第一个实现和评估。我们的实现表明，这种类型的SSE方案非常有效。</li></ol><h2 id="2、安全性"><a href="#2、安全性" class="headerlink" title="2、安全性"></a>2、安全性</h2><blockquote><p>服务器可以了解到一些客户端查询的有限信息。例如它知道正在搜索的关键字都包含在加密为$c_{w}$ 的文件中。</p></blockquote><p>SSE安全性</p><ul><li><p>（1）加密的索引 $\gamma$  和密文 $c$ 除了文件的数量 $n$ 和它们的长度之外，不泄露关于 $f$ 的任何信息；</p></li><li><p>（2）加密的索引 $\gamma$ 和令牌 $T_{w}$ 至多揭示了搜索 $I_{w}$ 的结果。</p></li></ul><p><strong>CKA1——抵抗选择关键字攻击的安全性</strong></p><ul><li>只有当客户的查询独立于索引和先前的结果时，才能保证安全性。</li></ul><p><strong>CKA2——抵抗自适应选择关键字攻击的安全性</strong></p><ul><li>即使客户端的查询是基于加密的索引和先前查询的结果，也能保证安全性。</li><li>即使存在可以在协议进行时破坏参与者的对手的情况下也能保持其安全性，则称其自适应安全。</li></ul><hr><p>动态SSE方案必须允许添加和删除文件。这两种操作都是使用令牌来处理的。</p><ul><li><p>为了添加文件 $f$，客户端生成添加令牌 $\tau_{a}$，给定  $\tau_{a}$ 和 $\gamma$，服务器可以更新加密的索引 $\gamma$。</p></li><li><p>为了删除文件 $f$，客户端生成删除令牌 $\tau_{d}$，服务器用它来更新 $\gamma$。</p></li></ul><p>动态SSE方案要求的安全保证：</p><ul><li><p>（1）给定加密的索引 $\gamma$ 和密文序列 $c$，任何对手都无法获知关于文件 $f$ 的任何信息；</p></li><li><p>（2）给定了 token 序列 $\tau&#x3D;(\tau_{1},…,\tau_{n})$。对于自适应生成的查询序列 $q&#x3D;(q_{1},…,q_{n})$（它可以用于搜索、添加或删除操作），任何对手都无法获知关于 $f$ 或 $q$ 的任何信息。</p></li></ul><h2 id="3、方案概述"><a href="#3、方案概述" class="headerlink" title="3、方案概述"></a>3、方案概述</h2><table><thead><tr><th align="center">符号</th><th align="left">含义</th></tr></thead><tbody><tr><td align="center">$f$</td><td align="left">明文序列 $f&#x3D;(f_{1},…,f_{n})$</td></tr><tr><td align="center">$f_{w}$</td><td align="left">包含$w$的明文</td></tr><tr><td align="center">$c$</td><td align="left">密文序列 $c&#x3D;(c_{1},…,c_{n})$</td></tr><tr><td align="center">$c_{w}$</td><td align="left">包含$w$的密文</td></tr><tr><td align="center">$I_{w}$</td><td align="left">包含$w$的文件标识符 $I_w∈c$</td></tr><tr><td align="center">$δ$</td><td align="left">索引</td></tr><tr><td align="center">$\gamma$</td><td align="left">加密的索引</td></tr><tr><td align="center">$\tau_{s}$</td><td align="left">搜索令牌</td></tr><tr><td align="center">$\tau_{a}$</td><td align="left">添加令牌</td></tr><tr><td align="center">$\tau_{d}$</td><td align="left">删除令牌</td></tr><tr><td align="center">$#f$</td><td align="left">$f$ 中文件的数量</td></tr><tr><td align="center">$#f_w$</td><td align="left">是包含关键字$w$的文件数量</td></tr><tr><td align="center">$#W$</td><td align="left">关键字空间的大小</td></tr></tbody></table><p>我们的方案是基于倒排索引SSE-1结构的扩展。尽管 SSE-1 是实用的（它对于小常数是渐近最优的），但它确实有一些限制，使得它不适合直接用于加密云存储系统中：</p><p>​    （1）它只能抵抗非自适应选择关键字攻击（CKA1），这意味着它只能为批量执行搜索的客户端提供安全性；</p><p>​    （2）它不是显式动态的，它只能支持使用通用和低效技术的动态操作。</p><p><strong>SSE-1 construction</strong></p><p>为了加密文件集合 $f$，该方案为每个关键字 $w∈W$ 构造一个列表 $L_{w}$，每个列表 $L_{w}$ 由 $#\rm f_{w}$ 个节点$(N_{1},…,N_{f_{w}})$，它们被存储在搜索数组 $A_s$ 中的随机位置。节点 $N_{i}$ 被定义为$N_{i}&#x3D;⟨id,addr_{s}(N_{i+1})⟩$，其中 $id$ 是包含 $w$ 的文件的唯一文件标识符，$addr_{s}(N)$ 表示在范围 $A_{s}$ 中的节点 $N$ 的位置。</p><blockquote><p>为了防止$A_{s}$的大小泄露有关 $f$ 的统计信息，建议$A_{s}$的大小至少为$|c|&#x2F;8$，和 使用长度适当的随机字符串填充未使用的单元格。</p></blockquote><p>对于每个关键字 $w$，将指向 $L_{w}$ 头部的指针插入到搜索表 $T_{s}$ 下的搜索键 $F_{K_{1}}(w)$中 ，然后在生成的密钥 $G_{K_{2}}(w)$ 下使用 SKE&#x3D; (Gen,Enc,Dec) 对每个列表进行加密。</p><p>如果要搜索关键字 w，客户端发送 $F_{K_{1}}(w)$ 和 $G_{K_{2}}(w)$ 就足够了。然后，服务器可以使用 $F_{K_{1}}(w)$ 和 $T_{s}$ 来恢复指向 $L_{w}$ 头部的指针，并使用 $G_{K_{2}}(w)$ 来解密列表并恢复包含 $w$ 的文件的标识符。只要 T 支持 O(1) 时间复杂度查找（可以使用哈希表实现），服务器的总搜索时间在 $#f_{w}$ 中是线性的，这是最优的。</p><hr><p>使用SSE-1动态。如上所述，SSE-1的限制有两方面：</p><p>（1）它只是CKA1-secure的</p><p>（2）它不是显式动态的。</p><p>第一个限制可以通过要求 SKE 是非承诺的就可以相对容易地解决(事实上，该工作中提出的 CKA2 安全 SSE 结构使用了一个简单的基于 PRF 的非承诺加密方案)。</p><ul><li>非承诺性（non-committin）：即方案的密文和安全参数与随机数是不可区分的！</li></ul><p>然而，第二个限制就不那么容易克服了。难点在于文件的添加、删除或修改需要服务器在存储在$A_{s}$中的加密列</p><p>表中添加、删除或修改节点。    这对于服务器来说很难做到，因为：</p><p>​    （1）当删除文件 $f$ 时，它不知道（在A中）与 $f$ 对应的节点存储在哪里；</p><p>​    （2）当从列表中插入或删除一个节点时，它不能修改前一个节点的指针，因为它是加密的；</p><p>​    （3）添加一个节点后，它不知道 $A_{s}$ 中哪些位置是空闲的。</p><p>​    在高层次上，我们处理这些限制如下：</p><ol><li><p>（文件删除）我们添加了一个额外的（加密的）数据结构$A_{d}$，称为删除数组，服务器可以查询（通过客户端提供的令牌），以恢复指向被删除文件对应节点的指针。更准确地说，删除数组为每个文件存储一个指向 $A_{s}$ 中的节点的节点列表，如果文件 $f$ 被删除，这些节点应该被删除。所以搜索数组中的每个节点在删除数组中都有一个对应的节点，删除数组中的每个节点都指向搜索数组中的一个节点。在整个过程中，我们将把这样的节点称为对偶，并编写$N^*$ 来指代节点$N$的对偶。</p></li><li><p>（指针修改）我们使用同态加密方案对存储在节点中的指针进行加密。这类似于[23]中van Liesdonk等人用来修改他们构造的加密搜索结构的方法。通过向服务器提供适当值的加密，它就可以修改指针，而不需要解密节点。我们使用“标准”对称加密方案，该方案包括将消息与PRF的输出进行异或运算。这个简单的构造还有一个优点，即不提交（在私有密钥设置中），我们可以利用这个优点来实现CKA2安全性。</p></li><li><p>（内存管理）为了跟踪$A_{s}$中的哪些位置是空闲的，我们添加并管理额外的空间，包括服务器用来添加新节点的free列表。</p></li></ol><h2 id="4、具体实现"><a href="#4、具体实现" class="headerlink" title="4、具体实现"></a>4、具体实现</h2><h3 id="Prepare"><a href="#Prepare" class="headerlink" title="Prepare"></a>Prepare</h3><p>$$<br>DSSE&#x3D;(Gen,Enc,SrchToken,AddToken,DelToken,Search,Add,Del,Dec)<br>$$</p><table><thead><tr><th align="center">序号</th><th align="center">算法</th><th align="center">描述</th></tr></thead><tbody><tr><td align="center">①</td><td align="center">$K\leftarrow Gen(1^{k})$</td><td align="center">密钥生成算法</td></tr><tr><td align="center">②</td><td align="center">$(\gamma,c)\leftarrow Enc(K,f)$</td><td align="center">索引生成算法</td></tr><tr><td align="center">③</td><td align="center">$\tau_{s}\leftarrow SrchToken(K,w)$</td><td align="center">陷门生成算法</td></tr><tr><td align="center">④</td><td align="center">$(\tau_{a},c_{f})\leftarrow AddToken(K,f)$</td><td align="center">添加令牌生成算法</td></tr><tr><td align="center">⑤</td><td align="center">$\tau_{d} \leftarrow DelToken(K,f)$</td><td align="center">删除令牌生成算法</td></tr><tr><td align="center">⑥</td><td align="center">$I_{w}:&#x3D;Search(\gamma,c,\tau_{s})$</td><td align="center">搜索算法</td></tr><tr><td align="center">⑦</td><td align="center">$(\gamma’,c’):&#x3D;Add(\gamma,c,\tau_{a},c_{new})$</td><td align="center">添加文件算法</td></tr><tr><td align="center">⑧</td><td align="center">$(\gamma’,c’):&#x3D;Del(\gamma,c,\tau_{d})$</td><td align="center">删除文件算法</td></tr><tr><td align="center">⑨</td><td align="center">$f:&#x3D;Dec(K,c)$</td><td align="center">解密算法</td></tr></tbody></table><table><thead><tr><th align="center">伪随机函数</th></tr></thead><tbody><tr><td align="center">$F:{0,1}^k×{0,1}^∗→{0,1}^k$</td></tr><tr><td align="center">$G:{0,1}^k×{0,1}^∗→{0,1}^∗$</td></tr><tr><td align="center">$P:{0,1}^k× {0,1}^∗→ {0,1}^k$</td></tr><tr><td align="center"><strong>随机预言机</strong></td></tr><tr><td align="center">$H_1:{0,1}^∗→ {0,1}^∗$</td></tr><tr><td align="center">$H_2:{0,1}^∗→{0,1}^∗$</td></tr></tbody></table><table><thead><tr><th align="center">符号</th><th align="center">描述</th></tr></thead><tbody><tr><td align="center">$A_s$</td><td align="center">搜索数组</td></tr><tr><td align="center">$A_{d}$</td><td align="center">删除数组</td></tr><tr><td align="center">$T_s$</td><td align="center">搜索表</td></tr><tr><td align="center">$T_{d}$</td><td align="center">删除表</td></tr></tbody></table><h3 id="Gen-1-k"><a href="#Gen-1-k" class="headerlink" title="$Gen(1^k)$"></a>$Gen(1^k)$</h3><p>$$<br>K &#x3D; (K_1，K_2，K_3，K_4)←{0, 1}^k<br>$$</p><h3 id="Enc-K-f"><a href="#Enc-K-f" class="headerlink" title="$Enc(K,f)$"></a>$Enc(K,f)$</h3><ol><li>设 $A_s$ 和 $A_d$ 为大小为 $|c|&#x2F;8 +z$ 的数组，设 $T_s$ 和 $T_d$ 分别为大小为 $#W$ 和 $#f$ 的字典。我们假设 <strong>0</strong> 是一个长度为$(log#A_s)$的 0 字符串，free 是一个不在 $W$ 中的单词</li></ol><ul><li>设 $z∈N$ 为 $free \ list$ 的初始大小</li></ul><ol start="2"><li>for $w∈W$</li></ol><p>创建一个列表 $L_w$ 包含 $#f_w$ 个节点 $(N_1，…，N_#f_w)$  存储在搜索数组 $A_s$ 的随机位置，并定义为<br>$$<br>N_i:&#x3D; (〈id_i,addr_s(N_{i+1})〉⊕H_1(K_w, r_i),r_i)<br>$$</p><ul><li>其中 $id_i$ 是 $f_w$ 中第 i 个文件的 ID，$r_i$ 是随机生成的 $k-bit$ 串，$K_w:&#x3D;P_{K_3}(w)$ , $addr_s(N_#f_{w+1}) &#x3D;0$</li></ul><p>通过设置 $T_s[F_{K_1}(w)] :&#x3D;〈addr_s(N_1),addr_d(N^⋆<em>1)〉⊕G</em>{K_2}(w)$ 存储一个指向搜索表中 $L_w$ 的第一个节点的指针</p><ul><li>其中 $N^*$ 是 N 的对偶，即$A_d$ 中的节点，其第四个入口指向$A_s$中的 $N_1$。</li></ul><ol start="3"><li>for $f∈\rm f$</li></ol><p>创建一个列表 $L_f$ 包含 $#f$ 个节点$(D_1，…，D_#f_w)$ 存储在搜索数组 $A_d$ 的随机位置，并定义为</p><p>$D_i:&#x3D; (〈addr_d(D_{i+1}),addr_d(N^⋆_{−1}),addr_d(N^⋆_{+1}),addr_s(N),addr_s(N_{−1}),addr_s(N_{+1}),F_{K_1}(w)〉⊕ H_2(K_f, r^′_i), r^′_i)$</p><ul><li>其中 $r’<em>i$ 是随机生成的 k-bit 串，$K_f:&#x3D;P</em>{K_3}(f)$ ， $addr_d(D_#f_{w+1}) &#x3D;0$</li><li>每个条目$D_i$都与一个单词 $w$ 相关，因此$L_w$中节点$N$。设$N_{+1}$为$L_w$中$N$之后的节点，$N_{-1}$为$L_w$中$N$之前的节点</li></ul><p>通过设置 $T_d[F_{K_1}(f)] :&#x3D;addr_d(D_1)⊕G_{K_2}(f)$，在删除表中存储一个指向 $L_f$ 的第一个节点的指针</p><ol start="4"><li>通过在 $A_s$ 和 $A_d$ 中随机选择 $z$ 个未使用的单元格，创建一个未加密的空闲列表 $L_{free}$，让$(F_1，…，F_z)$和$(F’_1，…，F’_z)$分别为 $A_s$ 和 $A_d$ 中的空闲节点。设置 $T_s[free] :&#x3D;〈addr_s(F_z),0^{log}#A〉$</li></ol><p>for $z≤i≤1$, 设置 $A_s[addr_s(F_i)] :&#x3D;0^{log}#f,addr_s(F_{i−1}),addr_d(F^′_i)$  其中 $addr_s(F_0) &#x3D;0^{log}#A$</p><ol start="5"><li><p>用随机字符串填充$A_S$ 和 $A_d$ 的其余条目</p></li><li><p>对于 $1≤i≤#f$，令 $c_i←SKE.Enc_{K4}(f_i)$</p></li><li><p>输出 $(γ,c)$ , 其中 $γ:&#x3D; (A_s,T_s,A_d,T_d)$ 和 $c&#x3D; (c_1, . . . , c_#f)$.</p></li></ol><h3 id="SrchToken-K-w"><a href="#SrchToken-K-w" class="headerlink" title="$SrchToken(K, w)$"></a>$SrchToken(K, w)$</h3><p>$$<br>τ_s:&#x3D;(F_{K_{1}}(w), G_{K_{2}}(w), P_{K_{3}}(w))<br>$$</p><h3 id="Search-γ-c-τ-s"><a href="#Search-γ-c-τ-s" class="headerlink" title="$Search(γ,c, τ_s)$"></a>$Search(γ,c, τ_s)$</h3><ul><li><p>将 $τ_s$ 解析为 $(τ_1， τ_2， τ_3)$，如果 $τ_1$ 在 $T_s$ 中不存在，则返回空列表。</p></li><li><p>通过计算 $(α_1， α’_1):&#x3D;T_s[τ_1]⊕τ_2$ 恢复指向列表第一个节点的指针</p></li><li><p>查找 $N_1:&#x3D;A[α_1]$ 并用 $τ_3$ 进行解密，</p></li><li><p>即将 $N_1$ 解析为 $(ν_1, r_1)$ 并计算$(id_1,0,addr_s(N_2)):&#x3D;ν_1⊕H_1(τ_3, r_1)$</p></li><li><p>对于 $i≥2$，解密节点 $N_i$，直到 $α_{i+1}&#x3D;0$</p></li><li><p>设 $I&#x3D;{id_1，…，id_m}$ 为前面步骤中显示的文件标识符，并输出${c_i}_{i∈I}$，即显示标识符的文件的加密</p></li></ul><h3 id="AddToken-K-f"><a href="#AddToken-K-f" class="headerlink" title="$AddToken(K, f)$"></a>$AddToken(K, f)$</h3><p>让 $(w_1,…,w_#f)$ 是 $f$ 中关键词。计算 $τ_a:&#x3D;(F_{K_{1}}(f), G_{K_{2}}(f), λ_1,. . .,λ_#f)$，对于所有 $1≤i≤#f$</p><p>$λ_i:&#x3D; (F_{K_{1}}(w_i), G_{K_{2}}(w_i),〈id(f),0〉 ⊕H_1(P_{K_3}(w_i), r_i), r_i,〈0,0,0,0,0,0, F_{K_{1}}(w_1〉 ⊕H_2(P_{K_3}(f), r^′_i), r^′_i)$</p><ul><li>$r_i$ 和 $r’<em>i$ 是随机位字符串。令 $c_f←SKE.Enc</em>{K_4}(f)$ </li><li>输出 $(τ_a, c_f)$</li></ul><h3 id="Add-γ-c-τ-a"><a href="#Add-γ-c-τ-a" class="headerlink" title="$Add(γ,c, τ_a)$"></a>$Add(γ,c, τ_a)$</h3><ol><li><p>将 $τ_a$ 解析为$(τ_1,τ_2, λ_1,. . .,λ_#f)$，如果 $τ_1$ 不在 $T_s$ 内，则是⊥(无法运算)</p></li><li><p>对于 $1≤i≤#f$</p></li></ol><ul><li><p>计算 $(φ,0) :&#x3D;T_s[free],  (φ_{−1}, φ^⋆) :&#x3D;A_s[φ]$ 找到搜索数组中最后一个空闲位置 $φ$ 和删除数组中对应入口 $φ^*$</p></li><li><p>通过设置 $T_s[free]:&#x3D; (φ_{−1},0)$，更新搜索表以指向倒数第二个空闲条目</p></li><li><p>通过计算 $(α_1,α^*_1):&#x3D;T_s[λ_i[1]]⊕λ_i[2]$ 恢复指向列表第一个节点 $N_1$ 的指针</p></li><li><p>将新节点存储在位置 $φ$ 处，并通过设置$A_s[φ]:&#x3D;(λ_i[3]⊕&lt; 0，α_1 &gt;， λ_i[4])$将其前向指针修改为$N_1$</p></li><li><p>更新搜索表  $T_s[λ_i[1]]:&#x3D;(φ, φ^⋆)⊕λ_i[2]$</p></li><li><p>更新 $N_1$的对偶  $A_d[α^⋆_1] :&#x3D;(D_1⊕〈0, φ^⋆,0,0,φ,0,0〉, r)$，其中 $(D_1,r) :&#x3D;A_d[α^⋆_1]$</p></li><li><p>更新$A_s[φ]$的对偶 $A_d[φ^⋆]:&#x3D;(λ_i[5]⊕〈φ^⋆_{−1},0, α^⋆_1,φ,0, α_1, λ_i[1]〉, λ_i[6])$</p></li><li><p>如果 i &#x3D; 1，则更新删除表  $T_d[τ_1] :&#x3D;〈φ^⋆,0〉⊕τ_2$</p></li></ul><ol start="3"><li>更新密文，将新密文 $c_{new}$ 加入 $c$</li></ol><h3 id="DelToken-K-f"><a href="#DelToken-K-f" class="headerlink" title="$DelToken(K, f)$"></a>$DelToken(K, f)$</h3><p>$$<br>τ_d:&#x3D; (F_{K_{1}}(f), G_{K_{2}}(f), P_{K_{3}}(f),id(f))<br>$$</p><h3 id="Del-γ-c-τ-d"><a href="#Del-γ-c-τ-d" class="headerlink" title="$Del(γ,c, τ_d)$"></a>$Del(γ,c, τ_d)$</h3><ol><li><p>将 $τ_d$ 解析为 $(τ_1,τ_2,τ_3,id)$，如果 $τ_1$ 不在 $T_d$ 内，则是⊥(终止)</p></li><li><p>找到 $L_f$ 的第一个节点 $α^′_1:&#x3D;T_d[τ_1]⊕τ_2$</p></li><li><p>对于 $1≤i≤#f$</p><ul><li><p>解密 $D_i$ ,  $(α_1, . . . , α_6, μ) :&#x3D;D_i⊕H_2(τ_3, r)$, 其中  $(D_i, r) :&#x3D;A_d[α^′_i]$</p></li><li><p>删除 $D_i$， $A_d[α^′_i]$ 为一个随机 ($6 log #(A+k)0$)-bit 字符串</p></li><li><p>找到最后一个空闲节点 $(φ,0^{log}#A) :&#x3D;T_s[free]$</p></li><li><p>使搜索表中的 free 条目指向$D_i$的对偶 $T_s[free]:&#x3D;〈α_4,0^{log}#A〉$</p></li><li><p>$D_i$ 对偶的 的自由位置 $As[α_4] :&#x3D; (φ, α^′_i)$</p></li></ul></li></ol><ul><li><p>让$N_{-1}$成为 $D_i$ 对偶之前的节点，更新 $N_{-1}$ 的 next 指针 by setting：</p></li><li><p>$A_s[α_5] :&#x3D; (β_1, β_2⊕α_4⊕α_6, r_{−1})$，其中 $(β_1, β_2, r_{−1}) :&#x3D;A_s[α_5]$ </p></li><li><p>此外 更新 $N_{-1}$的对偶指针 by setting:</p></li><li><p>$A_d[α_2] :&#x3D; (β_1, β_2, β_3⊕α^′<em>i⊕α_3, β_4, β_5, β_6⊕α_4⊕α_6, μ^∗, r^∗</em>{-1})$,where $(β_1, . . . , β_6, μ^∗, r^∗_{-1}) :&#x3D;A_d[α_2]$</p></li><li><p>让$N_{+1}$成为跟随 Di 的对偶的节点，更新 $N_{+1}$的对偶指针 by setting:</p></li><li><p>$A_d[α_3] :&#x3D; (β_1, β_2⊕α^′<em>i⊕α_2, β_3, β_4, β_5⊕α_4⊕α_5, β_6, μ^∗, r^∗</em>{+1})$，其中 $(β_1, . . . , β_6, μ^∗, r^∗_{+1}) :&#x3D;A_d[α_3]$</p></li><li><p>set $α^′_{i+1}:&#x3D;α_1$</p></li><li><p>4.从 $c$ 中删除 $id$ 对应的密文</p></li><li><p>5.从$T_d$中去除$τ_1$</p></li></ul><h3 id="Dec-K-c"><a href="#Dec-K-c" class="headerlink" title="$Dec(K, c)$"></a>$Dec(K, c)$</h3><p>$$<br>m:&#x3D;SKE.Dec_{K4}(c)<br>$$</p><h3 id="说明性例子"><a href="#说明性例子" class="headerlink" title="说明性例子"></a>说明性例子</h3><p>图1演示了包含3个文件和3个单词的玩具索引上的动态SSE数据结构。</p><p><img src="https://img.jwt1399.top/img/202211191913253.png"></p><p>该索引基于三个文档，即$f_{1}$、$f_{2}$、$f_{3}$，基于三个关键词，即 $w_{1}$、$w_{2}$、$w_{3}$。所有文档都包含关键字 $w_{1}$，关键字$w_{2}$仅包含在文档 $f_{2}$ 中，而 $w_{3}$ 包含在文档 $f_{2}$ 和 $f_{3}$ 中。</p><p>图1中还示出了相应的搜索表$T_{s}$、删除表 $T_{d}$、搜索数组 $A_{s}$ 和删除数组 $A_{d}$。</p><p>注意，在真实的DSSE索引中，会有填充来隐藏文件单词对的数量；在这个例子中，为了简单起见，我们省略了这个填充。</p><p><strong>搜索</strong>。搜索是我们方案中最简单的操作。</p><p>假设客户希望搜索包含关键字$w_{1}$的所有文档。他准备搜索令牌，其中包含 $F_{K_{1}}(w)$ 和$G_{K_{2}}(w)$。第一值 $F_{K_{1}}(w)$ 和将使服务器能够在搜索表$T_{s}$中定位对应于关键字$w_{1}$的条目。</p><p>在我们的例子中，这个值是$x&#x3D;(4∣∣1)\oplus G_{K_{2}}(w)$。服务器现在使用第二个值 和$G_{K_{2}}(w)$来计算$x\oplus G_{K_{2}}(w)$。这将允许服务器在搜索数组中定位正确的条目（在我们的例子中是4个），并开始“暴露”存储指向包含$w_{1}$的文档的指针的位置。这种去屏蔽是通过搜索令牌中包含的第三个值来执行的。</p><p><strong>添加文档</strong>。假设现在客户希望添加包含关键字$w_{1}$和$w_{2}$的文档$f_{4}$。请注意，搜索表根本没有改变，因为$f_{4}$正在进行成为关键字$w_{1}$和$w_{2}$列表中的最后一个条目，并且搜索表仅存储前几个条目。然而，所有其他数据结构必须以下列方式更新。首先，服务器使用无需快速检索搜索数组中“空闲”位置的索引，新条目将存储在这些位置。</p><p>在我们的例子中，这些位置是2和6。服务器在这些条目中存储新信息$(w_{1}，f_{4})$和$(w_{2},f_{4})$。现在，服务器需要将这个新条目连接到相应的关键字列表：使用add令牌，它在搜索数组中检索元素x和y的索引$i &#x3D; 0$和j &#x3D; 3，使得x和y对应于关键字列表$w_{1}$和$w_{2}$的最后条目。这样，服务器同态地将$A_{s}\left[ 0 \right]$和$A_{s}\left[ 3 \right]$的“下一个”指针设置为指向新添加的节点，这些节点已经存储在搜索数组的位置2和6。</p><p>注意，访问搜索数组中的空闲条目也提供了对删除数组$A_d$的相应空闲位置的访问。在我们的例子中，删除数组中空闲位置的索引是3和7。服务器将在删除数组中的这些位置存储新的条目$(f_{4},w_{1})$和$(f_{4},w_{2})$,并将它们用指针连接起来。最后，服务器将通过将条目$F_{K_{1}}(f_{4})$设置为指向删除数组中的位置3来更新删除表，以便稍后可以容易地检索文件$f_4$进行删除。</p><p> <strong>删除文档</strong>。假设现在客户想要删除一个已经存储在我们的索引中的文档，比如说文档$f_{3}$，它包含关键字$w_{1}$和$w_{3}$。删除是添加的“双重操作”。首先，服务器使用删除令牌的值$F_{K_{1}}(f_{3})$在删除表中定位正确的值$4\oplus G_{K_{2}}(f_{3})$。这将允许服务器访问剩余数据结构中需要用add算法以类似方式更新的部分。即它将“释放”删除数组中的位置4和6以及搜索数组中的位置1和3。当“释放”搜索数组中的位置时，它还将同态更新指向新条目的关键字列表$w_{1}$和$w_{3}$（在我们的例子中，指向列表的末尾——通常在已删除条目的下一个指针中）。注意，删除数组不需要这样的指针更新。</p><h2 id="5、参考文献"><a href="#5、参考文献" class="headerlink" title="5、参考文献"></a>5、参考文献</h2><ul><li><p><a href="https://blog.nowcoder.net/n/b3227a872c25451784acfbca3f9c2cb0">《动态可搜索对称加密》</a></p></li><li><p><a href="http://eotstxtab.top/2022/03/01/%E5%8A%A8%E6%80%81%E5%8F%AF%E6%90%9C%E7%B4%A2%E5%8A%A0%E5%AF%86-1-%E5%89%8D%E4%B8%96%E4%BB%8A%E7%94%9F/">动态可搜索加密-1-前世今生 - Kisna’s Blog (eotstxtab.top)</a></p></li><li><p><a href="http://it.taocms.org/07/110668.htm">IEEE TIFS’22：健壮且具有前后向安全性的可搜索对称加密_ IT技术精华 (taocms.org)</a></p></li><li><p><a href="https://link.springer.com/chapter/10.1007/978-3-319-98989-1_12">动态可搜索对称加密方案，支持具有向前（和向后）安全|的范围查询施普林格链接 (springer.com)</a></p></li><li><p><a href="https://www.cnblogs.com/pam-sh/p/16383012.html">安全性证明 - PamShao - 博客园 (cnblogs.com)</a></p></li></ul><h2 id="Sponsor❤️"><a href="#Sponsor❤️" class="headerlink" title="Sponsor❤️"></a>Sponsor❤️</h2><p>您的支持是我不断前进的动力，如果您感觉本文对您有所帮助的话，可以考虑打赏一下本文，用以维持本博客的运营费用，拒绝白嫖，从你我做起！🥰🥰🥰</p><table>  <tbody>     <tr>         <td style="text-align:center;">支付宝</td>         <td style="text-align:center;">微信</td>     </tr>   <tr>    <td style="text-align:center;" ><img width="200" src="https://jwt1399.top/medias/reward/alipay.png"></td>          <td style="text-align:center;"><img width="200" src="https://jwt1399.top/medias/reward/sponor_wechat.png"></td>       </tr></tbody></table>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;p&gt;&lt;strong&gt;论文名称：《Dynamic Searchable Symmetric Encryption》&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;作者：Seny Kamara、Charalampos Papamanthou、Tom Roeder</summary>
        
      
    
    
    
    <category term="Data" scheme="https://jwt1399.top/categories/Data/"/>
    
    
    <category term="可搜索加密" scheme="https://jwt1399.top/tags/%E5%8F%AF%E6%90%9C%E7%B4%A2%E5%8A%A0%E5%AF%86/"/>
    
  </entry>
  
  <entry>
    <title>SpringCloud-高级篇</title>
    <link href="https://jwt1399.top/posts/41649.html"/>
    <id>https://jwt1399.top/posts/41649.html</id>
    <published>2022-10-09T06:27:35.000Z</published>
    <updated>2022-11-20T12:36:21.730Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>高级篇包含微服务保护(流量控制，系统保护，熔断降级，服务授权)、分布式事务、多级缓存、Redis集群、可靠消息服务</p></blockquote><table><thead><tr><th><img src="https://img.jwt1399.top/img/202209201412578.png" alt="学习安排"></th></tr></thead><tbody><tr><td><img src="https://img.jwt1399.top/img/202209201436751.png" alt="技术分类"></td></tr></tbody></table><h1 id="1-微服务保护"><a href="#1-微服务保护" class="headerlink" title="1.微服务保护"></a>1.微服务保护</h1><h2 id="①初识Sentinel"><a href="#①初识Sentinel" class="headerlink" title="①初识Sentinel"></a>①初识Sentinel</h2><h3 id="❶雪崩问题及解决方案"><a href="#❶雪崩问题及解决方案" class="headerlink" title="❶雪崩问题及解决方案"></a>❶雪崩问题及解决方案</h3><p><strong>什么是雪崩问题？</strong></p><p>微服务中，服务间调用关系错综复杂，一个微服务往往依赖于多个其它微服务。如果微服务调用链路中的某个服务故障，引起整个链路中的所有微服务都不可用，则称为雪崩。</p><p><strong>解决雪崩问题的常见方式有四种：</strong></p><ul><li><p><strong>超时处理</strong>：设定超时时间，请求超过一定时间没有响应就返回错误信息，不会无休止等待。</p></li><li><p><strong>舱壁模式</strong>：限定每个业务能使用的线程数，避免耗尽整个tomcat的资源，因此也叫<strong>线程隔离</strong>。</p></li><li><p><strong>熔断降级</strong>：由<strong>断路器</strong>统计业务执行的异常比例，如果超出阈值则<strong>熔断</strong>该业务，拦截访问该业务的一切请求。</p></li><li><p><strong>流量控制</strong>：限制业务访问的QPS（Query Per Second：每秒处理请求数），避免服务因流量的突增而故障。</p></li></ul><p> <strong>流量控制</strong>是对服务的保护，避免因瞬间高并发流量而导致服务故障，进而避免雪崩。是一种<strong>预防</strong>措施。</p><p><strong>超时处理、线程隔离、熔断降级</strong>是在部分服务故障时，将故障控制在一定范围，避免雪崩。是一种<strong>补救</strong>措施。</p><p>换句话说：</p><p>如何避免因瞬间高并发流量而导致服务故障？流量控制</p><p>如何避免因服务故障引起的雪崩问题？超时处理、线程隔离、降级熔断</p><h3 id="❷服务保护技术对比"><a href="#❷服务保护技术对比" class="headerlink" title="❷服务保护技术对比"></a>❷服务保护技术对比</h3><p>在SpringCloud当中支持多种服务保护技术，早期比较流行的是Hystrix框架，但目前国内实用最广泛的还是阿里巴巴的Sentinel框架，这里我们做下对比：</p><table><thead><tr><th></th><th><strong>Sentinel</strong></th><th><strong>Hystrix</strong></th></tr></thead><tbody><tr><td>隔离策略</td><td>信号量隔离</td><td>线程池隔离&#x2F;信号量隔离</td></tr><tr><td>熔断降级策略</td><td>基于慢调用比例或异常比例</td><td>基于失败比率</td></tr><tr><td>实时指标实现</td><td>滑动窗口</td><td>滑动窗口（基于 RxJava）</td></tr><tr><td>规则配置</td><td>支持多种数据源</td><td>支持多种数据源</td></tr><tr><td>扩展性</td><td>多个扩展点</td><td>插件的形式</td></tr><tr><td>基于注解的支持</td><td>支持</td><td>支持</td></tr><tr><td>限流</td><td>基于 QPS，支持基于调用关系的限流</td><td>有限的支持</td></tr><tr><td>流量整形</td><td>支持慢启动、匀速排队模式</td><td>不支持</td></tr><tr><td>系统自适应保护</td><td>支持</td><td>不支持</td></tr><tr><td>控制台</td><td>开箱即用，可配置规则、查看秒级监控、机器发现等</td><td>不完善</td></tr><tr><td>常见框架的适配</td><td>Servlet、Spring Cloud、Dubbo、gRPC  等</td><td>Servlet、Spring Cloud Netflix</td></tr></tbody></table><h3 id="❸Sentinel介绍和安装"><a href="#❸Sentinel介绍和安装" class="headerlink" title="❸Sentinel介绍和安装"></a>❸Sentinel介绍和安装</h3><h4 id="1-介绍"><a href="#1-介绍" class="headerlink" title="1.介绍"></a>1.介绍</h4><p>Sentinel是阿里巴巴开源的一款微服务流量控制组件。官网地址：<a href="https://sentinelguard.io/zh-cn/index.html">https://sentinelguard.io/zh-cn/index.html</a></p><p>Sentinel 具有以下特征:</p><ul><li><p><strong>丰富的应用场景</strong>：Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景，例如秒杀（即突发流量控制在系统容量可以承受的范围）、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。</p></li><li><p><strong>完备的实时监控</strong>：Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据，甚至 500 台以下规模的集群的汇总运行情况。</p></li><li><p><strong>广泛的开源生态</strong>：Sentinel 提供开箱即用的与其它开源框架&#x2F;库的整合模块，例如与 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。</p></li><li><p><strong>完善的</strong> <strong>SPI</strong> <strong>扩展点</strong>：Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。</p></li></ul><h4 id="2-安装"><a href="#2-安装" class="headerlink" title="2.安装"></a>2.安装</h4><p><strong>下载</strong>：sentinel官方提供了UI控制台，可以在<a href="https://github.com/alibaba/Sentinel/releases">GitHub</a>下载。</p><p><strong>运行</strong>：将jar包放到任意非中文目录，执行命令：</p><pre class="line-numbers language-sh"><code class="language-sh">java -jar sentinel-dashboard-1.8.5.jar<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p><strong>修改配置</strong>：如果要修改Sentinel的默认端口、账户、密码，可以通过下列配置：</p><table><thead><tr><th><strong>配置项</strong></th><th><strong>默认值</strong></th><th><strong>说明</strong></th></tr></thead><tbody><tr><td>server.port</td><td>8080</td><td>服务端口</td></tr><tr><td>sentinel.dashboard.auth.username</td><td>sentinel</td><td>默认用户名</td></tr><tr><td>sentinel.dashboard.auth.password</td><td>sentinel</td><td>默认密码</td></tr></tbody></table><p>例如，修改端口：</p><pre class="line-numbers language-sh"><code class="language-sh">java -Dserver.port=8090 -jar sentinel-dashboard-1.8.5.jar<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p><strong>使用</strong>：访问<a href="http://localhost:8080页面，就可以看到sentinel的控制台了">http://localhost:8080页面，就可以看到sentinel的控制台了</a></p><h3 id="❹微服务整合Sentinel"><a href="#❹微服务整合Sentinel" class="headerlink" title="❹微服务整合Sentinel"></a>❹微服务整合Sentinel</h3><p>要使用Sentinel肯定要结合微服务，这里我们使用SpringCloud实用篇中的cloud-demo工程。</p><p>1）order-service中引入sentinel依赖</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token comment" spellcheck="true">&lt;!--sentinel--></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>com.alibaba.cloud<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>     <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-cloud-starter-alibaba-sentinel<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>2）配置控制台</p><p>修改application.yaml文件，添加下面内容：</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">server</span><span class="token punctuation">:</span>  <span class="token key atrule">port</span><span class="token punctuation">:</span> <span class="token number">8088</span><span class="token key atrule">spring</span><span class="token punctuation">:</span>  <span class="token key atrule">cloud</span><span class="token punctuation">:</span>     <span class="token key atrule">sentinel</span><span class="token punctuation">:</span>      <span class="token key atrule">transport</span><span class="token punctuation">:</span>        <span class="token key atrule">dashboard</span><span class="token punctuation">:</span> localhost<span class="token punctuation">:</span><span class="token number">8080</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>3）访问微服务的任意端点，访问后才能触发sentinel的监控。然后再访问sentinel的控制台，即可查看效果</p><h2 id="②流量控制"><a href="#②流量控制" class="headerlink" title="②流量控制"></a>②流量控制</h2><h3 id="❶簇点链路"><a href="#❶簇点链路" class="headerlink" title="❶簇点链路"></a>❶簇点链路</h3><p>当请求进入微服务时，首先会访问DispatcherServlet，然后进入Controller、Service、Mapper，这样的一个调用链就叫做<strong>簇点链路</strong>。簇点链路中被监控的每一个接口就是一个<strong>资源</strong>。</p><p>默认情况下sentinel会监控SpringMVC的每一个端点（Endpoint，也就是controller中的方法），因此SpringMVC的每一个端点（Endpoint）就是调用链路中的一个资源。</p><p>例如，我们刚才访问的order-service中的OrderController中的端点：&#x2F;order&#x2F;{orderId}</p><p><img src="https://img.jwt1399.top/img/202210101426510.png"></p><p>流控、熔断等都是针对簇点链路中的资源来设置的，因此我们可以点击对应资源后面的按钮来设置规则：</p><ul><li>流控：流量控制</li><li>降级：降级熔断</li><li>热点：热点参数限流，是限流的一种</li><li>授权：请求的权限控制</li></ul><h3 id="❷快速入门"><a href="#❷快速入门" class="headerlink" title="❷快速入门"></a>❷快速入门</h3><h4 id="1-示例"><a href="#1-示例" class="headerlink" title="1.示例"></a>1.示例</h4><p>点击资源&#x2F;order&#x2F;{orderId}后面的流控按钮，就可以弹出表单。表单中可以填写限流规则，如下：</p><p><img src="https://img.jwt1399.top/img/202210102031031.png"></p><p>其含义是限制 &#x2F;order&#x2F;{orderId}这个资源的单机QPS为1，即每秒只允许1次请求，超出的请求会被拦截并报错。</p><h4 id="2-练习"><a href="#2-练习" class="headerlink" title="2.练习"></a>2.练习</h4><blockquote><p>需求：给 &#x2F;order&#x2F;{orderId}这个资源设置流控规则，QPS不能超过 5，然后测试。</p></blockquote><p>1）首先在sentinel控制台添加限流规则</p><p>2）利用jmeter测试，如果没有用过jmeter，可以参考下面章节《Jmeter快速入门.md》</p><p>打开jmeter，导入编写好的Jmeter测试样例，选择<code>流控入门，QPS&lt;5</code><img src="https://img.jwt1399.top/img/202210101529813.png"></p><p>20个用户，2秒内运行完，QPS是10，超过了5。选中<code>流控入门，QPS&lt;5</code>右键运行：可以看到成功请求每次只有5个</p><blockquote><p>注意，不要点击菜单中的执行按钮来运行。</p></blockquote><table><thead><tr><th>启动</th><th>结果</th></tr></thead><tbody><tr><td><img src="https://img.jwt1399.top/img/202210101530894.png"></td><td><img src="https://img.jwt1399.top/img/202210101530202.png"></td></tr></tbody></table><h4 id="3-Jmeter快速入门"><a href="#3-Jmeter快速入门" class="headerlink" title="3.Jmeter快速入门"></a>3.Jmeter快速入门</h4><h5 id="1-安装Jmeter"><a href="#1-安装Jmeter" class="headerlink" title="1.安装Jmeter"></a>1.安装Jmeter</h5><p>Jmeter依赖于JDK，所以必须确保当前计算机上已经安装了JDK，并且配置了环境变量。</p><p>可以Apache Jmeter官网下载，地址：<a href="http://jmeter.apache.org/download_jmeter.cgi">http://jmeter.apache.org/download_jmeter.cgi</a></p><p>有两个版本可供下载：</p><ul><li>Binaries：二进制版，即已经编译好、可直接执行；</li><li>Source：源代码版，需要自己编译；</li></ul><p>我们下载Binaries版本，下载完成后，解压</p><h5 id="2-启动JMeter"><a href="#2-启动JMeter" class="headerlink" title="2.启动JMeter"></a>2.启动JMeter</h5><p>进入到bin目录下，通过<code>sh jmeter</code>命令来启动JMeter</p><p>现在，我们已经可以成功启动JMeter了，但是每次都需要打开终端、进入到JMeter的bin目录下，输入<code>sh jmeter</code>命令来启动，显得有点繁琐。当我们对~&#x2F;.bash_profile（ ~&#x2F;.zshrc）这个文件熟悉后，可以直接把JMeter配置到环境变量中。</p><p>还是通过<code>vim .bash_profile</code>进入到vim编辑器，输入以下命令：</p><pre class="line-numbers language-bash"><code class="language-bash"><span class="token function">export</span> JMETER_HOME<span class="token operator">=</span>/Users/jianjian/JavaSoft/apache-jmeter-5.5<span class="token function">export</span> PATH<span class="token operator">=</span><span class="token variable">$JAVA_HOME</span>/bin:<span class="token variable">$PATH</span>:.:<span class="token variable">$JMETER_HOME</span>/bin:<span class="token variable">$PATH</span><span class="token function">export</span> CLASSPATH<span class="token operator">=</span>.:<span class="token variable">$JAVA_HOME</span>/lib/dt.jar:<span class="token variable">$JAVA_HOME</span>/lib/tools.jar:<span class="token variable">$JMETER_HOME</span>/lib/ext/ApacheJMeter_core.jar:<span class="token variable">$JMETER_HOME</span>/lib/jorphan.jar:<span class="token variable">$JMETER_HOME</span>/lib/logkit-2.0.jar<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>退出vim编辑器，输入<code>source ~/.bash_profile</code>。接下来重点来了，直接在终端（任意目录）输入<code>jmeter</code>，即可启动JMeter。</p><h5 id="3-设置中文"><a href="#3-设置中文" class="headerlink" title="3.设置中文"></a>3.设置中文</h5><p>默认Jmeter的语言是英文，可以在 Option-&gt;Choose Lunguag中进行修改，这种方式只能保证本次运行是中文，如果要永久中文，需要修改Jmeter的配置文件</p><p>打开jmeter文件夹，在bin目录中找到 <strong>jmeter.properties</strong>，添加下面配置：</p><pre class="line-numbers language-bash"><code class="language-bash"><span class="token comment" spellcheck="true">#Preferred GUI language. Comment out to use the JVM default locale's language.</span>language<span class="token operator">=</span>zh_CN<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><h5 id="4-基本用法"><a href="#4-基本用法" class="headerlink" title="4.基本用法"></a>4.基本用法</h5><p>在测试计划上点鼠标右键，选择添加 &gt; 线程（用户） &gt; 线程组：</p><p><img src="https://img.jwt1399.top/img/202210102032741.png"></p><p>在新增的线程组中，填写线程信息：</p><p><img src="https://img.jwt1399.top/img/202210102032979.png"></p><p>给线程组点鼠标右键，添加http取样器：</p><p><img src="https://img.jwt1399.top/img/202210102032119.png"></p><p>编写取样器内容：</p><p><img src="https://img.jwt1399.top/img/202210102032649.png"></p><p>添加监听器：</p><table><thead><tr><th>添加监听报告</th><th>添加监听结果树</th></tr></thead><tbody><tr><td><img src="https://img.jwt1399.top/img/202210102032719.png"></td><td><img src="https://img.jwt1399.top/img/202210102032347.png"></td></tr></tbody></table><p>然后点击运行：</p><table><thead><tr><th>汇总报告结果</th><th>结果树</th></tr></thead><tbody><tr><td><img src="https://img.jwt1399.top/img/202210102032067.png"></td><td><img src="https://img.jwt1399.top/img/202210102032761.png"></td></tr></tbody></table><h3 id="❸流控模式"><a href="#❸流控模式" class="headerlink" title="❸流控模式"></a>❸流控模式</h3><h4 id="1-简介"><a href="#1-简介" class="headerlink" title="1.简介"></a>1.简介</h4><p>在添加限流规则时，点击高级选项，可以选择三种<strong>流控模式</strong>：</p><ul><li><strong>直接</strong>：统计当前资源的请求，触发阈值时对当前资源直接限流，默认的模式（快速入门案例就是直接模式）</li><li><strong>关联</strong>：统计与当前资源相关的另一个资源，触发阈值时，对当前资源限流</li><li><strong>链路</strong>：统计从指定链路访问到本资源的请求，触发阈值时，对指定链路限流</li></ul><p><img src="https://img.jwt1399.top/img/202210102043335.png"></p><h4 id="2-关联模式"><a href="#2-关联模式" class="headerlink" title="2.关联模式"></a>2.关联模式</h4><p><strong>关联模式</strong>：统计与当前资源相关的另一个资源触发阈值时，对当前资源限流</p><p><strong>配置规则</strong>：当&#x2F;write资源访问量触发阈值时，就会对&#x2F;read资源限流，避免影响&#x2F;write资源。</p><p><img src="https://img.jwt1399.top/img/202210102053981.png"></p><p>满足下面条件可以使用关联模式：</p><ul><li>两个有竞争关系的资源</li><li>一个优先级较高，一个优先级较低</li></ul><p><strong>例如</strong>：用户支付时需要修改订单状态，同时用户要查询订单。查询和修改操作会争抢数据库锁，产生竞争。业务需求是优先支付和更新订单的业务，因此当修改订单业务触发阈值时，需要对查询订单业务限流。</p><p><strong>练习</strong>：</p><ul><li><p>在OrderController新建两个端点：&#x2F;order&#x2F;query和&#x2F;order&#x2F;update，无需实现业务</p></li><li><p>配置流控规则，当&#x2F;order&#x2F;update资源被访问的QPS超过5时，对&#x2F;order&#x2F;query请求限流</p></li></ul><p>1）定义&#x2F;order&#x2F;query端点，模拟订单查询</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@GetMapping</span><span class="token punctuation">(</span><span class="token string">"/query"</span><span class="token punctuation">)</span><span class="token keyword">public</span> String <span class="token function">queryOrder</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">return</span> <span class="token string">"查询订单成功"</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>2）定义&#x2F;order&#x2F;update端点，模拟订单更新</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@GetMapping</span><span class="token punctuation">(</span><span class="token string">"/update"</span><span class="token punctuation">)</span><span class="token keyword">public</span> String <span class="token function">updateOrder</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">return</span> <span class="token string">"更新订单成功"</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>重启服务，即可查看sentinel控制台的簇点链路</p><p>3）配置流控规则</p><p>对哪个端点限流，就点击哪个端点后面的按钮。我们是对订单查询&#x2F;order&#x2F;query限流，因此点击它后面的按钮：</p><p><img src="https://img.jwt1399.top/img/202210102053322.png"></p><p>在表单中填写流控规则：</p><img src="https://img.jwt1399.top/img/202210102053327.png" style="zoom:50%;" /><p>4）在Jmeter测试，选择<code>流控模式-关联</code>：</p><p><img src="https://img.jwt1399.top/img/202210102056349.png"></p><p>可以看到1000个用户，100秒，因此QPS为10，超过了我们设定的阈值：5</p><p>查看http请求：</p><p><img src="https://img.jwt1399.top/img/202210102056241.png"></p><p>请求的目标是&#x2F;order&#x2F;update，超过阈值就会触发限流。但限流的目标是&#x2F;order&#x2F;query，在浏览器访问，可以发现：</p><p><img src="https://img.jwt1399.top/img/202210102056506.png"></p><h4 id="3-链路模式"><a href="#3-链路模式" class="headerlink" title="3.链路模式"></a>3.链路模式</h4><p><strong>链路模式</strong>：只针对从指定链路访问到本资源的请求做统计，判断是否超过阈值。</p><p><strong>配置示例</strong>：</p><p>例如有两条请求链路：</p><ul><li><p>&#x2F;test1 –&gt; &#x2F;common</p></li><li><p>&#x2F;test2 –&gt; &#x2F;common</p></li></ul><p>如果只希望统计从&#x2F;test2进入到&#x2F;common的请求，则可以这样配置：</p><p><img src="https://img.jwt1399.top/img/202210102100433.png"></p><p><strong>练习</strong></p><blockquote><p>需求：有查询订单和创建订单业务，两者都需要查询商品。针对从查询订单进入到查询商品的请求统计，并设置限流。</p></blockquote><p>步骤：</p><ol><li><p>在OrderService中添加一个queryGoods方法，不用实现业务</p></li><li><p>在OrderController中，改造&#x2F;order&#x2F;query端点，调用OrderService中的queryGoods方法</p></li><li><p>在OrderController中添加一个&#x2F;order&#x2F;save的端点，调用OrderService的queryGoods方法</p></li><li><p>给queryGoods设置限流规则，从&#x2F;order&#x2F;query进入queryGoods的方法限制QPS必须小于2</p></li></ol><p>实现：</p><p>1）添加查询商品方法</p><p>在order-service服务中，给OrderService类添加一个queryGoods方法：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">queryGoods</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>    System<span class="token punctuation">.</span>err<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"查询商品"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>2）查询订单时，查询商品</p><p>在order-service的OrderController中，修改&#x2F;order&#x2F;query端点的业务逻辑：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@GetMapping</span><span class="token punctuation">(</span><span class="token string">"/query"</span><span class="token punctuation">)</span><span class="token keyword">public</span> String <span class="token function">queryOrder</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 查询商品</span>    orderService<span class="token punctuation">.</span><span class="token function">queryGoods</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 查询订单</span>    System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"查询订单"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> <span class="token string">"查询订单成功"</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>3）新增订单，查询商品</p><p>在order-service的OrderController中，修改&#x2F;order&#x2F;save端点，模拟新增订单：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@GetMapping</span><span class="token punctuation">(</span><span class="token string">"/save"</span><span class="token punctuation">)</span><span class="token keyword">public</span> String <span class="token function">saveOrder</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 查询商品</span>    orderService<span class="token punctuation">.</span><span class="token function">queryGoods</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 查询订单</span>    System<span class="token punctuation">.</span>err<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"新增订单"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> <span class="token string">"新增订单成功"</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p> 4）给查询商品添加资源标记</p><p>默认情况下，OrderService中的方法是不被Sentinel监控的，需要我们自己通过注解来标记要监控的方法。</p><p>给OrderService的queryGoods方法添加<code>@SentinelResource</code>注解：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@SentinelResource</span><span class="token punctuation">(</span><span class="token string">"goods"</span><span class="token punctuation">)</span><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">queryGoods</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>    System<span class="token punctuation">.</span>err<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"查询商品"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>链路模式中，是对不同来源的两个链路做监控。但是sentinel默认会给进入SpringMVC的所有请求做context整合（设置同一个root资源），会导致链路模式失效。</p><p>我们需要关闭这种对SpringMVC的资源聚合，修改order-service服务的application.yml文件：</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">spring</span><span class="token punctuation">:</span>  <span class="token key atrule">cloud</span><span class="token punctuation">:</span>    <span class="token key atrule">sentinel</span><span class="token punctuation">:</span>      <span class="token key atrule">web-context-unify</span><span class="token punctuation">:</span> <span class="token boolean important">false </span><span class="token comment" spellcheck="true"># 关闭context整合</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>重启服务，访问&#x2F;order&#x2F;query和&#x2F;order&#x2F;save，可以查看到sentinel的簇点链路规则中，出现了新的资源：</p><img src="https://img.jwt1399.top/img/202210102105976.png" style="zoom:50%;" /><p>5）添加流控规则</p><p>点击goods资源后面的流控按钮，在弹出的表单中填写下面信息：</p><img src="../images/SpringCloud-高级篇/image-20221010210635083.png" style="zoom:50%;" /><p>只统计从&#x2F;order&#x2F;query进入&#x2F;goods的资源，QPS阈值为2，超出则被限流。</p><p> 6）Jmeter测试，选择<code>流控模式-链路</code>：</p><p><img src="https://img.jwt1399.top/img/202210102108736.png"></p><p>可以看到这里200个用户，50秒内发完，QPS为4，超过了我们设定的阈值2。</p><table><thead><tr><th>http请求一：&#x2F;order&#x2F;save</th><th>http请求二：&#x2F;order&#x2F;query</th></tr></thead><tbody><tr><td><img src="https://img.jwt1399.top/img/202210102110676.png"></td><td><img src="https://img.jwt1399.top/img/202210102110361.png"></td></tr><tr><td><img src="https://img.jwt1399.top/img/202210102110944.png"></td><td><img src="https://img.jwt1399.top/img/202210102110669.png"></td></tr></tbody></table><h4 id="4-总结"><a href="#4-总结" class="headerlink" title="4.总结"></a>4.总结</h4><p>流控模式有哪些？</p><ul><li><p>直接：对当前资源限流</p></li><li><p>关联：高优先级资源触发阈值，对低优先级资源限流。</p></li><li><p>链路：对请求来源的限流</p></li></ul><h3 id="❹流控效果"><a href="#❹流控效果" class="headerlink" title="❹流控效果"></a>❹流控效果</h3><h4 id="1-简介-1"><a href="#1-简介-1" class="headerlink" title="1.简介"></a>1.简介</h4><p>在流控的高级选项中，还有一个流控效果选项：</p><p>流控效果是指请求达到流控阈值时应该采取的措施，包括三种：</p><ul><li><p><strong>快速失败</strong>：达到阈值后，新的请求会被立即拒绝并抛出FlowException异常，是默认的处理方式。</p></li><li><p><strong>warm up</strong>：预热模式，对超出阈值的请求同样是拒绝并抛出异常。但这种模式阈值会动态变化，从一个较小值逐渐增加到最大阈值。</p></li><li><p><strong>排队等待</strong>：让所有的请求按照先后次序排队执行，两个请求的间隔不能小于指定时长</p></li></ul><p><img src="https://img.jwt1399.top/img/202210102117862.png"></p><h4 id="2-Warm-up"><a href="#2-Warm-up" class="headerlink" title="2.Warm up"></a>2.Warm up</h4><p>阈值一般是一个微服务能承担的最大QPS，但是一个服务刚刚启动时，一切资源尚未初始化（<strong>冷启动</strong>），如果直接将QPS跑到最大值，可能导致服务瞬间宕机。</p><p>warm up也叫<strong>预热模式</strong>，是应对服务冷启动的一种方案。请求阈值初始值是 <code>maxThreshold / coldFactor</code>，持续指定时长后，逐渐提高到 maxThreshold 值。而 coldFactor 的默认值是3。</p><p>例如，我设置QPS的maxThreshold为10，预热时间为5秒，那么初始阈值就是 10 &#x2F; 3 ，也就是3，然后在5秒后逐渐增长到10。</p><p><img src="https://img.jwt1399.top/img/202210102124732.png"></p><p><strong>练习</strong></p><blockquote><p>需求：给&#x2F;order&#x2F;{orderId}这个资源设置限流，最大QPS为10，利用warm up效果，预热时长为5秒</p></blockquote><p>1）配置流控规则：</p><img src="https://img.jwt1399.top/img/202210102126946.png"  style="zoom:50%;" /><p>2）Jmeter测试，选择<code>流控效果，warm up</code>：</p><p><img src="https://img.jwt1399.top/img/202210102126653.png"></p><table><thead><tr><th>刚刚启动时，大部分请求失败，成功的只有3个，说明QPS被限定在3</th><th>随着时间推移，成功比例越来越高</th></tr></thead><tbody><tr><td><img src="https://img.jwt1399.top/img/202210102131751.png"></td><td><img src="https://img.jwt1399.top/img/202210102131157.png"></td></tr><tr><td><img src="https://img.jwt1399.top/img/202210102131915.png"></td><td><img src="https://img.jwt1399.top/img/202210102131516.png"></td></tr></tbody></table><h4 id="3-排队等待"><a href="#3-排队等待" class="headerlink" title="3.排队等待"></a>3.排队等待</h4><p>当请求超过QPS阈值时，快速失败和warm up 会拒绝新的请求并抛出异常。而排队等待则是让所有请求进入一个队列中，然后按照阈值允许的时间间隔依次执行。后来的请求必须等待前面执行完成，如果请求的预期等待时间超出最大时长，则会被拒绝。</p><p>例如：QPS &#x3D; 5，意味着每200ms处理一个队列中的请求；timeout &#x3D; 2000，意味着<strong>预期等待时长</strong>超过2000ms的请求会被拒绝并抛出异常。</p><p>那什么叫做预期等待时长呢？比如现在一下子来了12 个请求，因为每200ms执行一个请求，那么：</p><ul><li>第6个请求的<strong>预期等待时长</strong> &#x3D;  200 * （6 - 1） &#x3D; 1000ms</li><li>第12个请求的预期等待时长 &#x3D; 200 * （12-1） &#x3D; 2200ms</li></ul><p><strong>练习</strong></p><blockquote><p>需求：给&#x2F;order&#x2F;{orderId}这个资源设置限流，最大QPS为10，利用排队的流控效果，超时时长设置为5s</p></blockquote><p>1）添加流控规则</p><img src="https://img.jwt1399.top/img/202210102138247.png" style="zoom:50%;" /><p>2）Jmeter测试，选择<code>流控效果，队列</code>：</p><p><img src="https://img.jwt1399.top/img/202210102139397.png"></p><p>如果是之前的 快速失败、warmup模式，超出的请求应该会直接报错。但是我们看看队列模式的运行结果：</p><p><img src="https://img.jwt1399.top/img/202210102140744.png"></p><p>全部都通过了。再去sentinel查看实时监控的QPS曲线：</p><p><img src="https://img.jwt1399.top/img/202210102142623.png"></p><p>QPS非常平滑，一致保持在10，超出的请求没有被拒绝，而是放入队列。因此<strong>响应时间</strong>（等待时间）会越来越长。当队列满了以后，才会有部分请求失败：</p><p><img src="https://img.jwt1399.top/img/202210102142008.png"></p><h4 id="4-总结-1"><a href="#4-总结-1" class="headerlink" title="4.总结"></a>4.总结</h4><p>流控效果有哪些？</p><ul><li><p>快速失败：QPS超过阈值时，拒绝新的请求</p></li><li><p>warm up： QPS超过阈值时，拒绝新的请求；QPS阈值逐渐提升，可以避免冷启动时高并发导致服务宕机。</p></li><li><p>排队等待：请求会进入队列，按照阈值允许的时间间隔依次执行请求；如果请求预期等待时长大于超时时间，直接拒绝</p></li></ul><h3 id="❺热点参数限流"><a href="#❺热点参数限流" class="headerlink" title="❺热点参数限流"></a>❺热点参数限流</h3><blockquote><p>之前的限流是统计访问某个资源的所有请求，判断是否超过QPS阈值。</p><p>而热点参数限流是<strong>分别统计参数值相同的请求</strong>，判断是否超过QPS阈值。</p></blockquote><h4 id="1-全局参数限流"><a href="#1-全局参数限流" class="headerlink" title="1.全局参数限流"></a>1.全局参数限流</h4><p>例如，一个根据id查询商品的接口：</p><p><img src="https://img.jwt1399.top/img/202210102155281.png"></p><p>访问&#x2F;goods&#x2F;{id}的请求中，id参数值会有变化，热点参数限流会根据参数值分别统计QPS，统计结果：</p><p><img src="https://img.jwt1399.top/img/202210102155180.png"></p><p>当id&#x3D;1的请求触发阈值被限流时，id≠1的请求不受影响。</p><p>配置示例：</p><p><img src="https://img.jwt1399.top/img/202210102159969.png"></p><p>代表的含义是：对hot这个资源的0号参数（第一个参数）做统计，每1秒<strong>相同参数值</strong>的请求数不能超过5</p><h4 id="2-热点参数限流"><a href="#2-热点参数限流" class="headerlink" title="2.热点参数限流"></a>2.热点参数限流</h4><p>刚才的配置中，对查询商品这个接口的所有商品一视同仁，QPS都限定为5.</p><p>而在实际开发中，可能部分商品是热点商品，例如秒杀商品，我们希望这部分商品的QPS限制与其它商品不一样。那就需要配置热点参数限流的高级选项了：</p><p><img src="https://img.jwt1399.top/img/202210102159086.png"></p><p>结合上一个配置，这里的含义是对0号的long类型参数限流，每1秒相同参数的QPS不能超过5，有两个例外：</p><ul><li><p>如果参数值是100，则每1秒允许的QPS为10</p></li><li><p>如果参数值是101，则每1秒允许的QPS为15</p></li></ul><h4 id="3-练习"><a href="#3-练习" class="headerlink" title="3.练习"></a>3.练习</h4><blockquote><p><strong>需求</strong>：给&#x2F;order&#x2F;{orderId}这个资源添加热点参数限流，规则如下：</p><ul><li><p>默认的热点参数规则是每1秒请求量不超过2</p></li><li><p>给102这个参数设置例外：每1秒请求量不超过4</p></li><li><p>给103这个参数设置例外：每1秒请求量不超过10</p></li></ul></blockquote><p><strong>注意事项</strong>：热点参数限流对默认的SpringMVC资源无效，需要利用<code>@SentinelResource</code>注解标记资源</p><p>1）标记资源</p><p>给order-service中的OrderController中的&#x2F;order&#x2F;{orderId}资源添加注解：</p><p><img src="https://img.jwt1399.top/img/202210102204455.png"></p><p>2）热点参数限流规则</p><p>访问该接口，可以看到我们标记的hot资源出现了：</p><p><img src="https://img.jwt1399.top/img/202210102204840.png"></p><p>这里不要点击hot后面的按钮，页面有BUG，点击左侧菜单中<strong>热点规则</strong>菜单：</p><p><img src="https://img.jwt1399.top/img/202210102204515.png"></p><p>点击新增，填写表单：</p><p><img src="https://img.jwt1399.top/img/202210102204647.png"></p><p>3）Jmeter测试，选择<code>热点参数限流 QPS1</code>：</p><p><img src="/../images/SpringCloud-%E9%AB%98%E7%BA%A7%E7%AF%87/image-20210716120754527.png" alt="image-20210716120754527"></p><p>包含3个http请求：</p><table><thead><tr><th>普通参数，QPS阈值为2</th><th>结果</th></tr></thead><tbody><tr><td><img src="https://img.jwt1399.top/img/202210102208321.png"></td><td><img src="https://img.jwt1399.top/img/202210102208371.png"></td></tr><tr><td><strong>例外项，QPS阈值为4</strong></td><td><strong>结果</strong></td></tr><tr><td><img src="https://img.jwt1399.top/img/202210102208534.png"></td><td><img src="https://img.jwt1399.top/img/202210102208122.png"></td></tr><tr><td><strong>例外项，QPS阈值为10</strong></td><td><strong>结果</strong></td></tr><tr><td><img src="https://img.jwt1399.top/img/202210102208460.png"></td><td><img src="https://img.jwt1399.top/img/202210102208539.png"></td></tr></tbody></table><h2 id="③隔离和降级"><a href="#③隔离和降级" class="headerlink" title="③隔离和降级"></a>③隔离和降级</h2><h3 id="❶Feign整合Sentinel"><a href="#❶Feign整合Sentinel" class="headerlink" title="❶Feign整合Sentinel"></a>❶Feign整合Sentinel</h3><p>SpringCloud中，微服务调用都是通过Feign来实现的，因此做客户端保护必须整合Feign和Sentinel。</p><p>Feign整合Sentinel的步骤：</p><ul><li>在application.yml中配置：feign.sentienl.enable&#x3D;true</li><li>给FeignClient编写FallbackFactory并注册为Bean</li><li>将FallbackFactory配置到FeignClient</li></ul><p><strong>1）修改配置，开启sentinel功能</strong></p><p>修改OrderService的application.yml文件，开启Feign的Sentinel功能：</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">feign</span><span class="token punctuation">:</span>  <span class="token key atrule">sentinel</span><span class="token punctuation">:</span>    <span class="token key atrule">enabled</span><span class="token punctuation">:</span> <span class="token boolean important">true </span><span class="token comment" spellcheck="true"># 开启feign对sentinel的支持</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p><strong>2）编写失败降级逻辑</strong></p><p>业务失败后，不能直接报错，而应该返回用户一个友好提示或者默认结果，这个就是失败降级逻辑。有两种方式：</p><p>①方式一：FallbackClass，无法对远程调用的异常做处理</p><p>②方式二：FallbackFactory，可以对远程调用的异常做处理，我们选择这种</p><p>这里我们演示方式二的失败降级处理。</p><p><strong>步骤一</strong>：在feing-api项目中定义类，实现FallbackFactory：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">package</span> cn<span class="token punctuation">.</span>itcast<span class="token punctuation">.</span>feign<span class="token punctuation">.</span>clients<span class="token punctuation">.</span>fallback<span class="token punctuation">;</span><span class="token annotation punctuation">@Slf4j</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">UserClientFallbackFactory</span> <span class="token keyword">implements</span> <span class="token class-name">FallbackFactory</span><span class="token operator">&lt;</span>UserClient<span class="token operator">></span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> UserClient <span class="token function">create</span><span class="token punctuation">(</span>Throwable throwable<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">UserClient</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token annotation punctuation">@Override</span>            <span class="token keyword">public</span> User <span class="token function">findById</span><span class="token punctuation">(</span>Long id<span class="token punctuation">)</span> <span class="token punctuation">{</span>                log<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token string">"查询用户异常"</span><span class="token punctuation">,</span> throwable<span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">User</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤二</strong>：在feing-api项目中的DefaultFeignConfiguration类中将UserClientFallbackFactory注册为一个Bean：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Bean</span><span class="token keyword">public</span> UserClientFallbackFactory <span class="token function">userClientFallbackFactory</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>    <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">UserClientFallbackFactory</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤三</strong>：在feing-api项目中的UserClient接口中使用UserClientFallbackFactory：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@FeignClient</span><span class="token punctuation">(</span>value <span class="token operator">=</span> <span class="token string">"userservice"</span><span class="token punctuation">,</span> fallbackFactory <span class="token operator">=</span> UserClientFallbackFactory<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">UserClient</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@GetMapping</span><span class="token punctuation">(</span><span class="token string">"/user/{id}"</span><span class="token punctuation">)</span>    User <span class="token function">findById</span><span class="token punctuation">(</span><span class="token annotation punctuation">@PathVariable</span><span class="token punctuation">(</span><span class="token string">"id"</span><span class="token punctuation">)</span> Long id<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>重启后，访问一次订单查询业务，然后查看sentinel控制台，可以看到新的簇点链路：</p><p><img src="https://img.jwt1399.top/img/202210121236062.png"></p><h3 id="❷线程隔离（舱壁模式）"><a href="#❷线程隔离（舱壁模式）" class="headerlink" title="❷线程隔离（舱壁模式）"></a>❷线程隔离（舱壁模式）</h3><h4 id="1-两种实现方式"><a href="#1-两种实现方式" class="headerlink" title="1.两种实现方式"></a>1.两种实现方式</h4><ul><li><p><strong>线程池隔离</strong>：给每个服务调用业务分配一个线程池，利用线程池本身实现隔离效果</p></li><li><p><strong>信号量隔离</strong>：采用计数器模式，记录业务使用线程数量，达到信号量上限时，禁止新请求(Sentinel默认采用)</p></li></ul><table><thead><tr><th>两种实现方式</th><th>各组优缺点</th></tr></thead><tbody><tr><td><img src="https://img.jwt1399.top/img/202210121328405.png"></td><td><img src="https://img.jwt1399.top/img/202210121328897.png"></td></tr></tbody></table><h4 id="2-sentinel的线程隔离"><a href="#2-sentinel的线程隔离" class="headerlink" title="2.sentinel的线程隔离"></a>2.sentinel的线程隔离</h4><p><strong>用法说明</strong>：</p><p>在添加限流规则时，可以选择两种阈值类型：</p><ul><li><p>QPS：每秒的请求数，在快速入门中已经演示过</p></li><li><p>线程数：该资源能使用的tomcat线程数的最大值。也就是通过限制线程数量，实现<strong>线程隔离</strong>（舱壁模式）。</p></li></ul><p><img src="https://img.jwt1399.top/img/202210121340548.png"></p><p><strong>实例练习：</strong></p><blockquote><p>需求：给 order-service 服务中的UserClient的查询用户接口设置流控规则，线程数不能超过 2。然后利用jmeter测试。</p></blockquote><p> 1）配置隔离规则，选择feign接口后面的流控按钮：</p><p><img src="https://img.jwt1399.top/img/202210121344508.png"></p><p>填写表单：</p><p><img src="https://img.jwt1399.top/img/202210121344944.png"></p><p>2）Jmeter测试，选择<code>阈值类型-线程数&lt;2</code>：</p><p><img src="https://img.jwt1399.top/img/202210121344384.png"></p><p>一次发生10个请求，有较大概率并发线程数超过2，而超出的请求会走之前定义的失败降级逻辑。</p><p>查看运行结果：</p><p><img src="https://img.jwt1399.top/img/202210121344849.png"></p><p>发现虽然结果都是通过了，不过部分请求得到的响应是降级返回的null信息。</p><h3 id="❸熔断降级"><a href="#❸熔断降级" class="headerlink" title="❸熔断降级"></a>❸熔断降级</h3><h4 id="1-简介-2"><a href="#1-简介-2" class="headerlink" title="1.简介"></a>1.简介</h4><p>熔断降级是解决雪崩问题的重要手段。其思路是由<strong>断路器</strong>统计服务调用的异常比例、慢请求比例，如果超出阈值则会<strong>熔断</strong>该服务。即拦截访问该服务的一切请求；而当服务恢复时，断路器会放行访问该服务的请求。</p><p>断路器控制熔断和放行是通过状态机来完成的：</p><p><img src="https://img.jwt1399.top/img/202210121349749.png"></p><p>状态机包括三个状态：</p><ul><li>closed：关闭状态，断路器放行所有请求，并开始统计异常比例、慢请求比例。超过阈值则切换到open状态</li><li>open：打开状态，服务调用被<strong>熔断</strong>，访问被熔断服务的请求会被拒绝，快速失败，直接走降级逻辑。Open状态5秒后会进入half-open状态</li><li>half-open：半开状态，放行一次请求，根据执行结果来判断接下来的操作。<ul><li>请求成功：则切换到closed状态</li><li>请求失败：则切换到open状态</li></ul></li></ul><p>断路器熔断策略有三种：慢调用、异常比例、异常数</p><h4 id="2-慢调用"><a href="#2-慢调用" class="headerlink" title="2.慢调用"></a>2.慢调用</h4><blockquote><p>业务的响应时长（RT）大于指定时长的请求认定为是慢调用请求。</p><p>在指定时间内，如果请求数量超过设定的最小数量，慢调用比例大于设定的阈值，则触发熔断。</p></blockquote><p>例如：</p><p><img src="https://img.jwt1399.top/img/202210121355334.png"></p><p>解读：RT超过500ms的调用是慢调用，统计最近10000ms内的请求，如果请求量超过10次，并且慢调用比例不低于0.5，则触发熔断，熔断时长为5秒。然后进入half-open状态，放行一次请求做测试。</p><p><strong>实例练习</strong></p><blockquote><p>需求：给 UserClient的查询用户接口设置降级规则，慢调用的RT阈值为50ms，统计时间为1秒，最小请求数量为5，失败阈值比例为0.4，熔断时长为5</p></blockquote><p>1）设置慢调用</p><p>修改user-service中的&#x2F;user&#x2F;{id}这个接口的业务。通过休眠模拟一个延迟时间：</p><p><img src="https://img.jwt1399.top/img/202210121359370.png"></p><table><thead><tr><th>orderId&#x3D;101的订单，关联的是id为1的用户，调用时长为60ms</th><th>orderId&#x3D;102的订单，关联的是id为2的用户，调用时长为非常短</th></tr></thead><tbody><tr><td><img src="https://img.jwt1399.top/img/202210121359056.png"></td><td><img src="https://img.jwt1399.top/img/202210121359372.png"></td></tr></tbody></table><p>2）设置熔断规则，给feign接口设置降级规则：</p><table><thead><tr><th><img src="https://img.jwt1399.top/img/202210121404112.png"></th></tr></thead><tbody><tr><td><img src="https://img.jwt1399.top/img/202210121404992.png"></td></tr></tbody></table><p>3）测试</p><p>在浏览器访问：<a href="http://localhost:8088/order/101%EF%BC%8C%E5%BF%AB%E9%80%9F%E5%88%B7%E6%96%B05%E6%AC%A1%EF%BC%8C%E5%8F%AF%E4%BB%A5%E5%8F%91%E7%8E%B0%EF%BC%9A">http://localhost:8088/order/101，快速刷新5次，可以发现：</a></p><p><img src="https://img.jwt1399.top/img/202210121411264.png"></p><p>触发了熔断，请求时长缩短至5ms，快速失败了，并且走降级逻辑，返回的null</p><p>在浏览器访问：<a href="http://localhost:8088/order/102%EF%BC%8C%E7%AB%9F%E7%84%B6%E4%B9%9F%E8%A2%AB%E7%86%94%E6%96%AD%E4%BA%86%EF%BC%9A">http://localhost:8088/order/102，竟然也被熔断了：</a></p><p><img src="https://img.jwt1399.top/img/202210121411138.png"></p><h4 id="3-异常比例、异常数"><a href="#3-异常比例、异常数" class="headerlink" title="3.异常比例、异常数"></a>3.异常比例、异常数</h4><blockquote><p>统计指定时间内的调用，如果调用次数超过指定请求数，并且出现异常的比例达到设定的比例阈值（或超过指定异常数），则触发熔断。</p></blockquote><p>异常比例设置：</p><p><img src="https://img.jwt1399.top/img/202210121417504.png"></p><p>解读：统计最近1000ms内的请求，如果请求量超过10次，并且异常比例不低于0.4，则触发熔断。</p><p>异常数设置：</p><p><img src="https://img.jwt1399.top/img/202210121417568.png"></p><p>解读：统计最近1000ms内的请求，如果请求量超过10次，并且异常数不低于2次，则触发熔断。</p><p><strong>实例练习</strong></p><blockquote><p>需求：给 UserClient的查询用户接口设置降级规则，统计时间为1秒，最小请求数量为5，失败阈值比例为0.4，熔断时长为5s</p></blockquote><p> 1）设置异常请求</p><p>首先，修改user-service中的&#x2F;user&#x2F;{id}这个接口的业务。手动抛出异常，以触发异常比例的熔断：</p><p><img src="https://img.jwt1399.top/img/202210121417647.png"></p><p>2）设置熔断规则，给feign接口设置降级规则：</p><table><thead><tr><th><img src="https://img.jwt1399.top/img/202210121417770.png"></th></tr></thead><tbody><tr><td><img src="https://img.jwt1399.top/img/202210121417510.png"></td></tr></tbody></table><p>在5次请求中，只要异常比例超过0.4，也就是有2次以上的异常，就会触发熔断。</p><p>3）测试</p><p>在浏览器快速访问：<a href="http://localhost:8088/order/102%EF%BC%8C%E5%BF%AB%E9%80%9F%E5%88%B7%E6%96%B05%E6%AC%A1%EF%BC%8C%E8%A7%A6%E5%8F%91%E7%86%94%E6%96%AD%EF%BC%9A">http://localhost:8088/order/102，快速刷新5次，触发熔断：</a></p><p><img src="https://img.jwt1399.top/img/202210121418855.png"></p><p>此时，我们去访问本来应该正常的103：</p><p><img src="https://img.jwt1399.top/img/202210121418963.png"></p><h2 id="④授权规则"><a href="#④授权规则" class="headerlink" title="④授权规则"></a>④授权规则</h2><h3 id="❶简介"><a href="#❶简介" class="headerlink" title="❶简介"></a>❶简介</h3><p>授权规则可以对请求方来源做判断和控制。有白名单和黑名单两种方式。</p><ul><li><p>白名单：来源（origin）在白名单内的调用者允许访问</p></li><li><p>黑名单：来源（origin）在黑名单内的调用者不允许访问</p></li></ul><p>点击左侧菜单的授权，可以看到授权规则：</p><p><img src="/../images/SpringCloud-%E9%AB%98%E7%BA%A7%E7%AF%87/image-20221013155050038.png"></p><ul><li><p>资源名：就是受保护的资源，例如&#x2F;order&#x2F;{orderId}</p></li><li><p>流控应用：是来源者的名单，</p><ul><li>如果是勾选白名单，则名单中的来源被许可访问。</li><li>如果是勾选黑名单，则名单中的来源被禁止访问。</li></ul></li></ul><p>比如：我们允许请求从gateway到order-service，不允许浏览器访问order-service，那么白名单中就要填写<strong>网关的来源名称（origin）</strong></p><p><img src="https://img.jwt1399.top/img/202210131552378.png"></p><h3 id="❷如何获取origin"><a href="#❷如何获取origin" class="headerlink" title="❷如何获取origin"></a>❷如何获取origin</h3><p>Sentinel是通过<code>RequestOriginParser</code>这个接口的<code>parseOrigin</code>来获取请求的来源的。</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">RequestOriginParser</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">/**     * 从请求request对象中获取origin，获取方式自定义     */</span>    String <span class="token function">parseOrigin</span><span class="token punctuation">(</span>HttpServletRequest request<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>这个方法的作用就是从request对象中，获取请求者的origin值并返回。默认情况下，sentinel不管请求者从哪里来，返回值永远是default，也就是说一切请求的来源都被认为是一样的值default。因此，我们需要自定义这个接口的实现，让<strong>不同的请求，返回不同的origin</strong>。</p><p>例如order-service服务中，我们定义一个RequestOriginParser的实现类：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">package</span> cn<span class="token punctuation">.</span>itcast<span class="token punctuation">.</span>order<span class="token punctuation">.</span>sentinel<span class="token punctuation">;</span><span class="token annotation punctuation">@Component</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">HeaderOriginParser</span> <span class="token keyword">implements</span> <span class="token class-name">RequestOriginParser</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> String <span class="token function">parseOrigin</span><span class="token punctuation">(</span>HttpServletRequest request<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 1.获取请求头</span>        String origin <span class="token operator">=</span> request<span class="token punctuation">.</span><span class="token function">getHeader</span><span class="token punctuation">(</span><span class="token string">"origin"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 2.非空判断</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>StringUtils<span class="token punctuation">.</span><span class="token function">isEmpty</span><span class="token punctuation">(</span>origin<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            origin <span class="token operator">=</span> <span class="token string">"blank"</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> origin<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="❸给网关添加请求头"><a href="#❸给网关添加请求头" class="headerlink" title="❸给网关添加请求头"></a>❸给网关添加请求头</h3><p>既然获取请求origin的方式是从reques-header中获取origin值，我们必须让<strong>所有从gateway路由到微服务的请求都带上origin头</strong>。这个需要利用之前学习的一个<code>GatewayFilter</code>来实现，<code>AddRequestHeaderGatewayFilter</code>。</p><p>修改gateway服务中的application.yml，添加一个defaultFilter：</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">spring</span><span class="token punctuation">:</span>  <span class="token key atrule">cloud</span><span class="token punctuation">:</span>    <span class="token key atrule">gateway</span><span class="token punctuation">:</span>      <span class="token key atrule">default-filters</span><span class="token punctuation">:</span>        <span class="token punctuation">-</span> AddRequestHeader=origin<span class="token punctuation">,</span>gateway      <span class="token key atrule">routes</span><span class="token punctuation">:</span>       <span class="token comment" spellcheck="true"># ...略</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>这样从gateway路由的所有请求都会带上origin头，值为gateway。而从其它地方到达微服务的请求则没有这个头。</p><h3 id="❹配置授权规则"><a href="#❹配置授权规则" class="headerlink" title="❹配置授权规则"></a>❹配置授权规则</h3><p>接下来，我们添加一个授权规则，放行origin值为gateway的请求。</p><table><thead><tr><th><img src="https://img.jwt1399.top/img/202210131558678.png"></th></tr></thead><tbody><tr><td><img src="https://img.jwt1399.top/img/202210131558364.png"></td></tr></tbody></table><table><thead><tr><th>跳过网关访问order-service服务</th><th>通过网关访问order-service服务</th></tr></thead><tbody><tr><td><img src="https://img.jwt1399.top/img/202210131559792.png"></td><td><img src="https://img.jwt1399.top/img/202210131559893.png"></td></tr></tbody></table><h2 id="⑤自定义异常结果"><a href="#⑤自定义异常结果" class="headerlink" title="⑤自定义异常结果"></a>⑤自定义异常结果</h2><blockquote><p>默认情况下，发生限流、降级、授权拦截时，都会抛出异常到调用方。异常结果都是flow limmiting（限流）。这样不够友好，无法得知是限流还是降级还是授权拦截。</p></blockquote><h3 id="❶异常类型"><a href="#❶异常类型" class="headerlink" title="❶异常类型"></a>❶异常类型</h3><p>如果要自定义异常时的返回结果，需要实现<code>BlockExceptionHandler</code>接口：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">BlockExceptionHandler</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">/**     * 处理请求被限流、降级、授权拦截时抛出的异常：BlockException     */</span>    <span class="token keyword">void</span> <span class="token function">handle</span><span class="token punctuation">(</span>HttpServletRequest request<span class="token punctuation">,</span> HttpServletResponse response<span class="token punctuation">,</span> BlockException e<span class="token punctuation">)</span> <span class="token keyword">throws</span> Exception<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>这个方法有三个参数：</p><ul><li>HttpServletRequest request：request对象</li><li>HttpServletResponse response：response对象</li><li>BlockException e：被sentinel拦截时抛出的异常</li></ul><p>这里的BlockException包含多个不同的子类：</p><table><thead><tr><th><strong>异常</strong></th><th><strong>说明</strong></th></tr></thead><tbody><tr><td>FlowException</td><td>限流异常</td></tr><tr><td>ParamFlowException</td><td>热点参数限流的异常</td></tr><tr><td>DegradeException</td><td>降级异常</td></tr><tr><td>AuthorityException</td><td>授权规则异常</td></tr><tr><td>SystemBlockException</td><td>系统规则异常</td></tr></tbody></table><h3 id="❷异常处理"><a href="#❷异常处理" class="headerlink" title="❷异常处理"></a>❷异常处理</h3><p>下面，我们就在order-service定义一个自定义异常处理类：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">package</span> cn<span class="token punctuation">.</span>itcast<span class="token punctuation">.</span>order<span class="token punctuation">.</span>sentinel<span class="token punctuation">;</span><span class="token annotation punctuation">@Component</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">SentinelExceptionHandler</span> <span class="token keyword">implements</span> <span class="token class-name">BlockExceptionHandler</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">handle</span><span class="token punctuation">(</span>HttpServletRequest request<span class="token punctuation">,</span> HttpServletResponse response<span class="token punctuation">,</span> BlockException e<span class="token punctuation">)</span> <span class="token keyword">throws</span> Exception <span class="token punctuation">{</span>        String msg <span class="token operator">=</span> <span class="token string">"未知异常"</span><span class="token punctuation">;</span>        <span class="token keyword">int</span> status <span class="token operator">=</span> <span class="token number">429</span><span class="token punctuation">;</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>e <span class="token keyword">instanceof</span> <span class="token class-name">FlowException</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            msg <span class="token operator">=</span> <span class="token string">"请求被限流了"</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>e <span class="token keyword">instanceof</span> <span class="token class-name">ParamFlowException</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            msg <span class="token operator">=</span> <span class="token string">"请求被热点参数限流"</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>e <span class="token keyword">instanceof</span> <span class="token class-name">DegradeException</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            msg <span class="token operator">=</span> <span class="token string">"请求被降级了"</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>e <span class="token keyword">instanceof</span> <span class="token class-name">AuthorityException</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            msg <span class="token operator">=</span> <span class="token string">"没有权限访问"</span><span class="token punctuation">;</span>            status <span class="token operator">=</span> <span class="token number">401</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        response<span class="token punctuation">.</span><span class="token function">setContentType</span><span class="token punctuation">(</span><span class="token string">"application/json;charset=utf-8"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        response<span class="token punctuation">.</span><span class="token function">setStatus</span><span class="token punctuation">(</span>status<span class="token punctuation">)</span><span class="token punctuation">;</span>        response<span class="token punctuation">.</span><span class="token function">getWriter</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"{\"msg\": "</span> <span class="token operator">+</span> msg <span class="token operator">+</span> <span class="token string">", \"status\": "</span> <span class="token operator">+</span> status <span class="token operator">+</span> <span class="token string">"}"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>重启测试，在不同场景下，会返回不同的异常消息。</p><table><thead><tr><th>限流：</th><th>授权拦截：</th></tr></thead><tbody><tr><td><img src="https://img.jwt1399.top/img/202210131607851.png"></td><td><img src="https://img.jwt1399.top/img/202210131607100.png"></td></tr></tbody></table><h2 id="⑥规则持久化"><a href="#⑥规则持久化" class="headerlink" title="⑥规则持久化"></a>⑥规则持久化</h2><h3 id="❶简介-1"><a href="#❶简介-1" class="headerlink" title="❶简介"></a>❶简介</h3><p>现在，sentinel的所有规则都是内存存储，重启后所有规则都会丢失。在生产环境下，我们必须确保这些规则的持久化，避免丢失。</p><p>规则是否能持久化，取决于规则管理模式，sentinel支持三种规则管理模式：</p><ul><li>原始模式：Sentinel的默认模式，将规则保存在内存，重启服务会丢失。</li><li>pull模式</li><li>push模式</li></ul><h3 id="❷pull模式"><a href="#❷pull模式" class="headerlink" title="❷pull模式"></a>❷pull模式</h3><p>pull模式：控制台将配置的规则推送到Sentinel客户端，而客户端会将配置规则保存在本地文件或数据库中。以后会定时去本地文件或数据库中查询，更新本地规则。</p><p><img src="https://img.jwt1399.top/img/202210131614663.png"></p><h3 id="❸push模式"><a href="#❸push模式" class="headerlink" title="❸push模式"></a>❸push模式</h3><p>push模式：控制台将配置规则推送到远程配置中心，例如Nacos。Sentinel客户端监听Nacos，获取配置变更的推送消息，完成本地配置更新。</p><img src="https://img.jwt1399.top/img/202210131614311.png" style="zoom: 67%;" /><blockquote><p>需求：修改OrderService，让其监听Nacos中的sentinel规则配置。</p></blockquote><h4 id="1-引入依赖"><a href="#1-引入依赖" class="headerlink" title="1.引入依赖"></a>1.引入依赖</h4><p>在order-service中引入sentinel监听nacos的依赖：</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>com.alibaba.csp<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>sentinel-datasource-nacos<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><h4 id="2-配置nacos地址"><a href="#2-配置nacos地址" class="headerlink" title="2.配置nacos地址"></a>2.配置nacos地址</h4><p>在order-service中的application.yml文件配置nacos地址及监听的配置信息：</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">spring</span><span class="token punctuation">:</span>  <span class="token key atrule">cloud</span><span class="token punctuation">:</span>    <span class="token key atrule">sentinel</span><span class="token punctuation">:</span>      <span class="token key atrule">datasource</span><span class="token punctuation">:</span>        <span class="token key atrule">flow</span><span class="token punctuation">:</span>          <span class="token key atrule">nacos</span><span class="token punctuation">:</span>            <span class="token key atrule">server-addr</span><span class="token punctuation">:</span> localhost<span class="token punctuation">:</span><span class="token number">8848 </span><span class="token comment" spellcheck="true"># nacos地址</span>            <span class="token key atrule">dataId</span><span class="token punctuation">:</span> orderservice<span class="token punctuation">-</span>flow<span class="token punctuation">-</span>rules            <span class="token key atrule">groupId</span><span class="token punctuation">:</span> SENTINEL_GROUP            <span class="token key atrule">rule-type</span><span class="token punctuation">:</span> flow <span class="token comment" spellcheck="true"># 还可以是：degrade、authority、param-flow</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="3-修改Sentinel-dashboard源码"><a href="#3-修改Sentinel-dashboard源码" class="headerlink" title="3.修改Sentinel-dashboard源码"></a>3.修改Sentinel-dashboard源码</h4><blockquote><p>SentinelDashboard默认不支持nacos的持久化，需要修改源码。</p></blockquote><p>下载sentinel-dashboard源码包，用IDEA打开</p><h5 id="1）修改nacos依赖"><a href="#1）修改nacos依赖" class="headerlink" title="1）修改nacos依赖"></a>1）修改nacos依赖</h5><p>在sentinel-dashboard源码的pom文件中，nacos的依赖默认的scope是test，只能在测试时使用，这里要去除</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>com.alibaba.csp<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>sentinel-datasource-nacos<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><h5 id="2）添加nacos支持"><a href="#2）添加nacos支持" class="headerlink" title="2）添加nacos支持"></a>2）添加nacos支持</h5><p>在sentinel-dashboard的test包下，已经编写了对nacos的支持，我们需要将其拷贝到main下。</p><p><img src="https://img.jwt1399.top/img/202210131625772.png"></p><h5 id="3）修改nacos地址"><a href="#3）修改nacos地址" class="headerlink" title="3）修改nacos地址"></a>3）修改nacos地址</h5><p>然后，还需要修改测试代码中的NacosConfig类：</p><p><img src="https://img.jwt1399.top/img/202210131628968.png"></p><p>修改其中的nacos地址，让其读取application.properties中的配置：</p><img src="https://img.jwt1399.top/img/202210131628323.png" style="zoom:67%;" /><p>在sentinel-dashboard的application.properties中添加nacos地址配置：</p><pre class="line-numbers language-properties"><code class="language-properties"><span class="token attr-name">nacos.addr</span><span class="token punctuation">=</span><span class="token attr-value">localhost:8848</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><h5 id="4）配置nacos数据源"><a href="#4）配置nacos数据源" class="headerlink" title="4）配置nacos数据源"></a>4）配置nacos数据源</h5><p>另外，还需要修改com.alibaba.csp.sentinel.dashboard.controller.v2包下的FlowControllerV2类：</p><p><img src="https://img.jwt1399.top/img/202210131629440.png"></p><p>让我们添加的Nacos数据源生效：</p><p><img src="https://img.jwt1399.top/img/202210131629311.png"></p><h5 id="5）修改前端页面"><a href="#5）修改前端页面" class="headerlink" title="5）修改前端页面"></a>5）修改前端页面</h5><p>接下来，还要修改前端页面，添加一个支持nacos的菜单。</p><p>修改src&#x2F;main&#x2F;webapp&#x2F;resources&#x2F;app&#x2F;scripts&#x2F;directives&#x2F;sidebar&#x2F;目录下的sidebar.html文件：</p><p><img src="https://img.jwt1399.top/img/202210131630519.png"></p><p>将其中的这部分注释打开：</p><p><img src="https://img.jwt1399.top/img/202210131630133.png"></p><p>修改其中的文本：</p><p><img src="https://img.jwt1399.top/img/202210131630652.png"></p><h5 id="6）重新编译、打包项目"><a href="#6）重新编译、打包项目" class="headerlink" title="6）重新编译、打包项目"></a>6）重新编译、打包项目</h5><p>运行IDEA中的maven插件，编译和打包修改好的Sentinel-Dashboard：</p><p><img src="https://img.jwt1399.top/img/202210131630645.png"></p><h5 id="7）启动"><a href="#7）启动" class="headerlink" title="7）启动"></a>7）启动</h5><p>启动方式跟官方一样：</p><pre class="line-numbers language-sh"><code class="language-sh">java -jar sentinel-dashboard.jar<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>如果要修改nacos地址，需要添加参数：</p><pre class="line-numbers language-sh"><code class="language-sh">java -jar -Dnacos.addr=localhost:8848 sentinel-dashboard.jar<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><h1 id="2-分布式事务"><a href="#2-分布式事务" class="headerlink" title="2.分布式事务"></a>2.分布式事务</h1><h2 id="➀事务分类"><a href="#➀事务分类" class="headerlink" title="➀事务分类"></a>➀事务分类</h2><p><strong>本地事务</strong>，也就是基于关系型数据库的事务，也称为传统事务。在传统数据库事务中，必须要满足ACID原则：</p><ul><li><p>原子性（atomicity）：事务要么全部提交成功，要么全部失败回滚</p></li><li><p>一致性（consistency）：事务的执行不能破坏数据库数据的完整性和一致性</p></li><li><p>隔离性（isolation）：对同一资源操作的事务不能同时发生</p></li><li><p>持久性（durability）：对数据库做的一切修改将永久保存，不管是否出现故障</p></li></ul><p><strong>分布式事务</strong>，在分布式系统环境下由不同的服务之间通过网络远程协作完成事务。例如：</p><ul><li>跨数据源的分布式事务</li><li>跨服务的分布式事务</li><li>综合情况</li></ul><p><strong>举个🌰：</strong></p><p>例如电商行业中比较常见的下单付款案例，包括：创建新订单、扣减商品库存、从用户账户余额扣除金额</p><p>完成上面的操作需要访问三个不同的微服务和三个不同的数据库。</p><p><img src="/../images/SpringCloud-%E9%AB%98%E7%BA%A7%E7%AF%87/image-20221015145400942.png"></p><p>订单的创建、库存的扣减、账户扣款在每一个服务和数据库内是一个<strong>本地事务</strong>，可以保证ACID原则。</p><p>把三件事情看做一个”业务”，要保证所有业务最终状态一致，这就是<strong>分布式系统下的事务</strong>了。此时ACID难以满足，这是分布式事务要解决的问题</p><h2 id="➁理论基础"><a href="#➁理论基础" class="headerlink" title="➁理论基础"></a>➁理论基础</h2><h3 id="❶CAP定理"><a href="#❶CAP定理" class="headerlink" title="❶CAP定理"></a>❶CAP定理</h3><img src="https://img.jwt1399.top/img/202210151459285.png" style="zoom:50%;" /><p>1998年，加州大学的计算机科学家 Eric Brewer 提出，分布式系统有三个指标。</p><ul><li><p><strong>Consistency（一致性）</strong>：用户访问分布式系统中的任意节点，得到的数据必须一致。</p></li><li><p><strong>Availability（可用性）</strong>：用户访问集群中的任意健康节点，必须能得到响应，而不是超时或拒绝。</p></li><li><p><strong>Partition tolerance （分区容错性）</strong>：因网络故障或其它原因导致分布式系统中的部分节点与其它节点失去连接，形成独立分区时，整个系统也要持续对外提供服务</p></li></ul><p>它们的第一个字母分别是 C、A、P。Eric Brewer 说，分布式系统节点通过网络连接，一定会出现分区问题（P）</p><p>当分区出现时，系统的一致性（C）和可用性（A）就无法同时满足。即这<strong>三个指标不可能同时做到</strong>，这个结论就</p><p>叫做 <strong>CAP 定理</strong>。</p><h3 id="❷BASE理论"><a href="#❷BASE理论" class="headerlink" title="❷BASE理论"></a>❷BASE理论</h3><p>BASE理论是对CAP的一种解决思路，包含三个思想：</p><ul><li><strong>Basically Available</strong> <strong>（基本可用）</strong>：分布式系统在出现故障时，允许损失部分可用性，即保证核心可用。</li><li><strong>Soft State（软状态）：</strong>在一定时间内，允许出现中间状态，比如临时的不一致状态。</li><li><strong>Eventually Consistent（最终一致性）</strong>：虽然无法保证强一致性，但是在软状态结束后，最终达到数据一致。</li></ul><h3 id="❸解决分布式事务的思路"><a href="#❸解决分布式事务的思路" class="headerlink" title="❸解决分布式事务的思路"></a>❸解决分布式事务的思路</h3><p>分布式事务最大的问题是各个子事务的一致性问题，因此可以借鉴CAP定理和BASE理论，有两种解决思路：</p><ul><li><p>AP模式：各子事务分别执行和提交，允许出现结果不一致，然后采用弥补措施恢复数据即可，实现最终一致。</p></li><li><p>CP模式：各子事务执行后互相等待，同时提交和回滚，达成强一致。但事务等待过程中，处于弱可用状态。</p></li></ul><h2 id="➂初识Seata"><a href="#➂初识Seata" class="headerlink" title="➂初识Seata"></a>➂初识Seata</h2><blockquote><p>Seata是 2019 年 1 月份蚂蚁金服和阿里巴巴共同开源的分布式事务解决方案。致力于提供高性能和简单易用的分布式事务服务，为用户打造一站式的分布式解决方案。官网地址：<a href="https://seata.io/">https://seata.io/</a></p></blockquote><h3 id="❶Seata的架构"><a href="#❶Seata的架构" class="headerlink" title="❶Seata的架构"></a>❶Seata的架构</h3><p>Seata事务管理中有三个重要的角色：</p><ul><li><p><strong>TC (Transaction Coordinator) -</strong> <strong>事务协调者：</strong>维护全局和分支事务的状态，协调全局事务提交或回滚。</p></li><li><p><strong>TM (Transaction Manager) -</strong> <strong>事务管理器：</strong>定义全局事务的范围、开始全局事务、提交或回滚全局事务。</p></li><li><p><strong>RM (Resource Manager) -</strong> <strong>资源管理器：</strong>管理分支事务处理的资源，与TC交谈以注册分支事务和报告分支事务的状态，并驱动分支事务提交或回滚。</p></li></ul><p><img src="https://img.jwt1399.top/img/202210151519221.png" alt="架构图"></p><p>Seata基于上述架构提供了四种不同的分布式事务解决方案：</p><ul><li>XA模式：强一致性分阶段事务模式，牺牲了一定的可用性，无业务侵入</li><li>AT模式：最终一致的分阶段事务模式，无业务侵入，也是Seata的默认模式</li><li>TCC模式：最终一致的分阶段事务模式，有业务侵入</li><li>SAGA模式：长事务模式，有业务侵入</li></ul><p>无论哪种方案，都离不开TC，也就是事务的协调者。</p><h3 id="❷部署TC服务"><a href="#❷部署TC服务" class="headerlink" title="❷部署TC服务"></a>❷部署TC服务</h3><p>1.首先我们要下载seata-server包并解压，地址：<a href="https://seata.io/zh-cn/blog/download.html">下载中心 (seata.io)</a></p><p>2.修改配置：修改conf目录下的registry.conf文件：</p><pre class="line-numbers language-properties"><code class="language-properties"><span class="token attr-name">registry</span> <span class="token attr-value">{</span><span class="token comment" spellcheck="true">  # tc服务的注册中心类，这里选择nacos，也可以是eureka、zookeeper等</span><span class="token attr-name">  type</span> <span class="token punctuation">=</span> <span class="token attr-value">"nacos"</span><span class="token attr-name">  nacos</span> <span class="token attr-value">{</span><span class="token comment" spellcheck="true">    # seata tc 服务注册到 nacos的服务名称，可以自定义</span><span class="token attr-name">    application</span> <span class="token punctuation">=</span> <span class="token attr-value">"seata-tc-server"</span><span class="token attr-name">    serverAddr</span> <span class="token punctuation">=</span> <span class="token attr-value">"127.0.0.1:8848"</span><span class="token attr-name">    group</span> <span class="token punctuation">=</span> <span class="token attr-value">"DEFAULT_GROUP"</span><span class="token attr-name">    namespace</span> <span class="token punctuation">=</span> <span class="token attr-value">""</span><span class="token attr-name">    cluster</span> <span class="token punctuation">=</span> <span class="token attr-value">"SH"</span><span class="token attr-name">    username</span> <span class="token punctuation">=</span> <span class="token attr-value">"nacos"</span><span class="token attr-name">    password</span> <span class="token punctuation">=</span> <span class="token attr-value">"nacos"</span>  }}<span class="token attr-name">config</span> <span class="token attr-value">{</span><span class="token comment" spellcheck="true">  # 读取tc服务端的配置文件的方式，这里是从nacos配置中心读取，这样如果tc是集群，可以共享配置</span><span class="token attr-name">  type</span> <span class="token punctuation">=</span> <span class="token attr-value">"nacos"</span><span class="token comment" spellcheck="true">  # 配置nacos地址等信息</span><span class="token attr-name">  nacos</span> <span class="token attr-value">{</span><span class="token attr-name">    serverAddr</span> <span class="token punctuation">=</span> <span class="token attr-value">"127.0.0.1:8848"</span><span class="token attr-name">    namespace</span> <span class="token punctuation">=</span> <span class="token attr-value">""</span><span class="token attr-name">    group</span> <span class="token punctuation">=</span> <span class="token attr-value">"SEATA_GROUP"</span><span class="token attr-name">    username</span> <span class="token punctuation">=</span> <span class="token attr-value">"nacos"</span><span class="token attr-name">    password</span> <span class="token punctuation">=</span> <span class="token attr-value">"nacos"</span><span class="token attr-name">    dataId</span> <span class="token punctuation">=</span> <span class="token attr-value">"seataServer.properties"</span>  }}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>3.在nacos添加配置，为了让tc服务的集群可以共享配置，我们选择了nacos作为统一配置中心。因此服务端配置文件seataServer.properties文件需要在nacos中配好。在nacos中新建配置文件seataServer.properties</p><pre class="line-numbers language-properties"><code class="language-properties"><span class="token comment" spellcheck="true"># 数据存储方式，db代表数据库</span><span class="token attr-name">store.mode</span><span class="token punctuation">=</span><span class="token attr-value">db</span><span class="token attr-name">store.db.datasource</span><span class="token punctuation">=</span><span class="token attr-value">druid</span><span class="token attr-name">store.db.dbType</span><span class="token punctuation">=</span><span class="token attr-value">mysql</span><span class="token attr-name">store.db.driverClassName</span><span class="token punctuation">=</span><span class="token attr-value">com.mysql.jdbc.Driver</span><span class="token attr-name">store.db.url</span><span class="token punctuation">=</span><span class="token attr-value">jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&amp;rewriteBatchedStatements=true</span><span class="token attr-name">store.db.user</span><span class="token punctuation">=</span><span class="token attr-value">root</span><span class="token attr-name">store.db.password</span><span class="token punctuation">=</span><span class="token attr-value">root</span><span class="token attr-name">store.db.minConn</span><span class="token punctuation">=</span><span class="token attr-value">5</span><span class="token attr-name">store.db.maxConn</span><span class="token punctuation">=</span><span class="token attr-value">30</span><span class="token attr-name">store.db.globalTable</span><span class="token punctuation">=</span><span class="token attr-value">global_table</span><span class="token attr-name">store.db.branchTable</span><span class="token punctuation">=</span><span class="token attr-value">branch_table</span><span class="token attr-name">store.db.queryLimit</span><span class="token punctuation">=</span><span class="token attr-value">100</span><span class="token attr-name">store.db.lockTable</span><span class="token punctuation">=</span><span class="token attr-value">lock_table</span><span class="token attr-name">store.db.maxWait</span><span class="token punctuation">=</span><span class="token attr-value">5000</span><span class="token comment" spellcheck="true"># 事务、日志等配置</span><span class="token attr-name">server.recovery.committingRetryPeriod</span><span class="token punctuation">=</span><span class="token attr-value">1000</span><span class="token attr-name">server.recovery.asynCommittingRetryPeriod</span><span class="token punctuation">=</span><span class="token attr-value">1000</span><span class="token attr-name">server.recovery.rollbackingRetryPeriod</span><span class="token punctuation">=</span><span class="token attr-value">1000</span><span class="token attr-name">server.recovery.timeoutRetryPeriod</span><span class="token punctuation">=</span><span class="token attr-value">1000</span><span class="token attr-name">server.maxCommitRetryTimeout</span><span class="token punctuation">=</span><span class="token attr-value">-1</span><span class="token attr-name">server.maxRollbackRetryTimeout</span><span class="token punctuation">=</span><span class="token attr-value">-1</span><span class="token attr-name">server.rollbackRetryTimeoutUnlockEnable</span><span class="token punctuation">=</span><span class="token attr-value">false</span><span class="token attr-name">server.undo.logSaveDays</span><span class="token punctuation">=</span><span class="token attr-value">7</span><span class="token attr-name">server.undo.logDeletePeriod</span><span class="token punctuation">=</span><span class="token attr-value">86400000</span><span class="token comment" spellcheck="true"># 客户端与服务端传输方式</span><span class="token attr-name">transport.serialization</span><span class="token punctuation">=</span><span class="token attr-value">seata</span><span class="token attr-name">transport.compressor</span><span class="token punctuation">=</span><span class="token attr-value">none</span><span class="token comment" spellcheck="true"># 关闭metrics功能，提高性能</span><span class="token attr-name">metrics.enabled</span><span class="token punctuation">=</span><span class="token attr-value">false</span><span class="token attr-name">metrics.registryType</span><span class="token punctuation">=</span><span class="token attr-value">compact</span><span class="token attr-name">metrics.exporterList</span><span class="token punctuation">=</span><span class="token attr-value">prometheus</span><span class="token attr-name">metrics.exporterPrometheusPort</span><span class="token punctuation">=</span><span class="token attr-value">9898</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>4.创建数据库表：tc服务在管理分布式事务时，需要记录事务相关数据到数据库中，你需要提前创建好这些表。</p><p>新建一个名为seata的数据库，运行提供的sql文件：这些表主要记录全局事务、分支事务、全局锁信息：</p><pre class="line-numbers language-sql"><code class="language-sql"><span class="token keyword">SET</span> NAMES utf8mb4<span class="token punctuation">;</span><span class="token keyword">SET</span> FOREIGN_KEY_CHECKS <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">-- ----------------------------</span><span class="token comment" spellcheck="true">-- 分支事务表</span><span class="token comment" spellcheck="true">-- ----------------------------</span><span class="token keyword">DROP</span> <span class="token keyword">TABLE</span> <span class="token keyword">IF</span> <span class="token keyword">EXISTS</span> <span class="token punctuation">`</span>branch_table<span class="token punctuation">`</span><span class="token punctuation">;</span><span class="token keyword">CREATE</span> <span class="token keyword">TABLE</span> <span class="token punctuation">`</span>branch_table<span class="token punctuation">`</span>  <span class="token punctuation">(</span>  <span class="token punctuation">`</span>branch_id<span class="token punctuation">`</span> <span class="token keyword">bigint</span><span class="token punctuation">(</span><span class="token number">20</span><span class="token punctuation">)</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span>  <span class="token punctuation">`</span>xid<span class="token punctuation">`</span> <span class="token keyword">varchar</span><span class="token punctuation">(</span><span class="token number">128</span><span class="token punctuation">)</span> <span class="token keyword">CHARACTER SET</span> utf8 <span class="token keyword">COLLATE</span> utf8_general_ci <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span>  <span class="token punctuation">`</span>transaction_id<span class="token punctuation">`</span> <span class="token keyword">bigint</span><span class="token punctuation">(</span><span class="token number">20</span><span class="token punctuation">)</span> <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span>  <span class="token punctuation">`</span>resource_group_id<span class="token punctuation">`</span> <span class="token keyword">varchar</span><span class="token punctuation">(</span><span class="token number">32</span><span class="token punctuation">)</span> <span class="token keyword">CHARACTER SET</span> utf8 <span class="token keyword">COLLATE</span> utf8_general_ci <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span>  <span class="token punctuation">`</span>resource_id<span class="token punctuation">`</span> <span class="token keyword">varchar</span><span class="token punctuation">(</span><span class="token number">256</span><span class="token punctuation">)</span> <span class="token keyword">CHARACTER SET</span> utf8 <span class="token keyword">COLLATE</span> utf8_general_ci <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span>  <span class="token punctuation">`</span>branch_type<span class="token punctuation">`</span> <span class="token keyword">varchar</span><span class="token punctuation">(</span><span class="token number">8</span><span class="token punctuation">)</span> <span class="token keyword">CHARACTER SET</span> utf8 <span class="token keyword">COLLATE</span> utf8_general_ci <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span>  <span class="token punctuation">`</span><span class="token keyword">status</span><span class="token punctuation">`</span> <span class="token keyword">tinyint</span><span class="token punctuation">(</span><span class="token number">4</span><span class="token punctuation">)</span> <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span>  <span class="token punctuation">`</span>client_id<span class="token punctuation">`</span> <span class="token keyword">varchar</span><span class="token punctuation">(</span><span class="token number">64</span><span class="token punctuation">)</span> <span class="token keyword">CHARACTER SET</span> utf8 <span class="token keyword">COLLATE</span> utf8_general_ci <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span>  <span class="token punctuation">`</span>application_data<span class="token punctuation">`</span> <span class="token keyword">varchar</span><span class="token punctuation">(</span><span class="token number">2000</span><span class="token punctuation">)</span> <span class="token keyword">CHARACTER SET</span> utf8 <span class="token keyword">COLLATE</span> utf8_general_ci <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span>  <span class="token punctuation">`</span>gmt_create<span class="token punctuation">`</span> <span class="token keyword">datetime</span><span class="token punctuation">(</span><span class="token number">6</span><span class="token punctuation">)</span> <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span>  <span class="token punctuation">`</span>gmt_modified<span class="token punctuation">`</span> <span class="token keyword">datetime</span><span class="token punctuation">(</span><span class="token number">6</span><span class="token punctuation">)</span> <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span>  <span class="token keyword">PRIMARY</span> <span class="token keyword">KEY</span> <span class="token punctuation">(</span><span class="token punctuation">`</span>branch_id<span class="token punctuation">`</span><span class="token punctuation">)</span> <span class="token keyword">USING</span> <span class="token keyword">BTREE</span><span class="token punctuation">,</span>  <span class="token keyword">INDEX</span> <span class="token punctuation">`</span>idx_xid<span class="token punctuation">`</span><span class="token punctuation">(</span><span class="token punctuation">`</span>xid<span class="token punctuation">`</span><span class="token punctuation">)</span> <span class="token keyword">USING</span> <span class="token keyword">BTREE</span><span class="token punctuation">)</span> <span class="token keyword">ENGINE</span> <span class="token operator">=</span> <span class="token keyword">InnoDB</span> <span class="token keyword">CHARACTER SET</span> <span class="token operator">=</span> utf8 <span class="token keyword">COLLATE</span> <span class="token operator">=</span> utf8_general_ci ROW_FORMAT <span class="token operator">=</span> Compact<span class="token punctuation">;</span><span class="token comment" spellcheck="true">-- ----------------------------</span><span class="token comment" spellcheck="true">-- 全局事务表</span><span class="token comment" spellcheck="true">-- ----------------------------</span><span class="token keyword">DROP</span> <span class="token keyword">TABLE</span> <span class="token keyword">IF</span> <span class="token keyword">EXISTS</span> <span class="token punctuation">`</span>global_table<span class="token punctuation">`</span><span class="token punctuation">;</span><span class="token keyword">CREATE</span> <span class="token keyword">TABLE</span> <span class="token punctuation">`</span>global_table<span class="token punctuation">`</span>  <span class="token punctuation">(</span>  <span class="token punctuation">`</span>xid<span class="token punctuation">`</span> <span class="token keyword">varchar</span><span class="token punctuation">(</span><span class="token number">128</span><span class="token punctuation">)</span> <span class="token keyword">CHARACTER SET</span> utf8 <span class="token keyword">COLLATE</span> utf8_general_ci <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span>  <span class="token punctuation">`</span>transaction_id<span class="token punctuation">`</span> <span class="token keyword">bigint</span><span class="token punctuation">(</span><span class="token number">20</span><span class="token punctuation">)</span> <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span>  <span class="token punctuation">`</span><span class="token keyword">status</span><span class="token punctuation">`</span> <span class="token keyword">tinyint</span><span class="token punctuation">(</span><span class="token number">4</span><span class="token punctuation">)</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span>  <span class="token punctuation">`</span>application_id<span class="token punctuation">`</span> <span class="token keyword">varchar</span><span class="token punctuation">(</span><span class="token number">32</span><span class="token punctuation">)</span> <span class="token keyword">CHARACTER SET</span> utf8 <span class="token keyword">COLLATE</span> utf8_general_ci <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span>  <span class="token punctuation">`</span>transaction_service_group<span class="token punctuation">`</span> <span class="token keyword">varchar</span><span class="token punctuation">(</span><span class="token number">32</span><span class="token punctuation">)</span> <span class="token keyword">CHARACTER SET</span> utf8 <span class="token keyword">COLLATE</span> utf8_general_ci <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span>  <span class="token punctuation">`</span>transaction_name<span class="token punctuation">`</span> <span class="token keyword">varchar</span><span class="token punctuation">(</span><span class="token number">128</span><span class="token punctuation">)</span> <span class="token keyword">CHARACTER SET</span> utf8 <span class="token keyword">COLLATE</span> utf8_general_ci <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span>  <span class="token punctuation">`</span>timeout<span class="token punctuation">`</span> <span class="token keyword">int</span><span class="token punctuation">(</span><span class="token number">11</span><span class="token punctuation">)</span> <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span>  <span class="token punctuation">`</span>begin_time<span class="token punctuation">`</span> <span class="token keyword">bigint</span><span class="token punctuation">(</span><span class="token number">20</span><span class="token punctuation">)</span> <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span>  <span class="token punctuation">`</span>application_data<span class="token punctuation">`</span> <span class="token keyword">varchar</span><span class="token punctuation">(</span><span class="token number">2000</span><span class="token punctuation">)</span> <span class="token keyword">CHARACTER SET</span> utf8 <span class="token keyword">COLLATE</span> utf8_general_ci <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span>  <span class="token punctuation">`</span>gmt_create<span class="token punctuation">`</span> <span class="token keyword">datetime</span> <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span>  <span class="token punctuation">`</span>gmt_modified<span class="token punctuation">`</span> <span class="token keyword">datetime</span> <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span>  <span class="token keyword">PRIMARY</span> <span class="token keyword">KEY</span> <span class="token punctuation">(</span><span class="token punctuation">`</span>xid<span class="token punctuation">`</span><span class="token punctuation">)</span> <span class="token keyword">USING</span> <span class="token keyword">BTREE</span><span class="token punctuation">,</span>  <span class="token keyword">INDEX</span> <span class="token punctuation">`</span>idx_gmt_modified_status<span class="token punctuation">`</span><span class="token punctuation">(</span><span class="token punctuation">`</span>gmt_modified<span class="token punctuation">`</span><span class="token punctuation">,</span> <span class="token punctuation">`</span><span class="token keyword">status</span><span class="token punctuation">`</span><span class="token punctuation">)</span> <span class="token keyword">USING</span> <span class="token keyword">BTREE</span><span class="token punctuation">,</span>  <span class="token keyword">INDEX</span> <span class="token punctuation">`</span>idx_transaction_id<span class="token punctuation">`</span><span class="token punctuation">(</span><span class="token punctuation">`</span>transaction_id<span class="token punctuation">`</span><span class="token punctuation">)</span> <span class="token keyword">USING</span> <span class="token keyword">BTREE</span><span class="token punctuation">)</span> <span class="token keyword">ENGINE</span> <span class="token operator">=</span> <span class="token keyword">InnoDB</span> <span class="token keyword">CHARACTER SET</span> <span class="token operator">=</span> utf8 <span class="token keyword">COLLATE</span> <span class="token operator">=</span> utf8_general_ci ROW_FORMAT <span class="token operator">=</span> Compact<span class="token punctuation">;</span><span class="token keyword">SET</span> FOREIGN_KEY_CHECKS <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>5.启动TC服务：进入bin目录，运行其中的<code>seata-server.sh</code>即可，启动成功后，seata-server应该已经注册到nacos注册中心了。</p><p>打开浏览器，访问nacos地址：<a href="http://localhost:8848，然后进入服务列表页面，可以看到seata-tc-server的信息">http://localhost:8848，然后进入服务列表页面，可以看到seata-tc-server的信息</a></p><h3 id="❸微服务集成Seata"><a href="#❸微服务集成Seata" class="headerlink" title="❸微服务集成Seata"></a>❸微服务集成Seata</h3><p>我们通过一个案例来演示分布式事务的问题：seata-demo</p><ul><li><p>1）创建数据库，名为seata_demo，然后导入提供的seata-demo.sql文件：</p></li><li><p>2）将项目seata-demo导入IDEA</p></li></ul><p>其中：</p><p>seata-demo：父工程，负责管理项目依赖</p><ul><li>account-service：账户服务，负责管理用户的资金账户。提供扣减余额的接口</li><li>storage-service：库存服务，负责管理商品库存。提供扣减库存的接口</li><li>order-service：订单服务，负责管理订单。创建订单时，需要调用account-service和storage-service</li></ul><p>我们以order-service为例来演示，其它两个微服务也都参考order-service的步骤来做，完全一样。</p><h4 id="1-引入依赖-1"><a href="#1-引入依赖-1" class="headerlink" title="1.引入依赖"></a>1.引入依赖</h4><p>首先，在order-service中引入依赖：</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token comment" spellcheck="true">&lt;!--seata--></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>com.alibaba.cloud<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-cloud-starter-alibaba-seata<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>exclusions</span><span class="token punctuation">></span></span>        <span class="token comment" spellcheck="true">&lt;!--版本较低，1.3.0，因此排除--></span>         <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>exclusion</span><span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>seata-spring-boot-starter<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>io.seata<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>exclusion</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>exclusions</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>io.seata<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>seata-spring-boot-starter<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>    <span class="token comment" spellcheck="true">&lt;!--seata starter 采用1.4.2版本--></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>${seata.version}<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="2-配置TC地址"><a href="#2-配置TC地址" class="headerlink" title="2.配置TC地址"></a>2.配置TC地址</h4><p>在order-service中的application.yml中，配置TC服务信息，通过注册中心nacos，结合服务名称获取TC地址：</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">seata</span><span class="token punctuation">:</span>  <span class="token key atrule">registry</span><span class="token punctuation">:</span> <span class="token comment" spellcheck="true"># TC服务注册中心的配置，微服务根据这些信息去注册中心获取tc服务地址</span>    <span class="token key atrule">type</span><span class="token punctuation">:</span> nacos <span class="token comment" spellcheck="true"># 注册中心类型 nacos</span>    <span class="token key atrule">nacos</span><span class="token punctuation">:</span>      <span class="token key atrule">server-addr</span><span class="token punctuation">:</span> 127.0.0.1<span class="token punctuation">:</span><span class="token number">8848 </span><span class="token comment" spellcheck="true"># nacos地址</span>      <span class="token key atrule">namespace</span><span class="token punctuation">:</span> <span class="token string">""</span> <span class="token comment" spellcheck="true"># namespace，默认为空</span>      <span class="token key atrule">group</span><span class="token punctuation">:</span> DEFAULT_GROUP <span class="token comment" spellcheck="true"># 分组，默认是DEFAULT_GROUP</span>      <span class="token key atrule">application</span><span class="token punctuation">:</span> seata<span class="token punctuation">-</span>tc<span class="token punctuation">-</span>server <span class="token comment" spellcheck="true"># seata服务名称</span>      <span class="token key atrule">username</span><span class="token punctuation">:</span> nacos      <span class="token key atrule">password</span><span class="token punctuation">:</span> nacos  <span class="token key atrule">tx-service-group</span><span class="token punctuation">:</span> seata<span class="token punctuation">-</span>demo <span class="token comment" spellcheck="true"># 事务组名称</span>  <span class="token key atrule">service</span><span class="token punctuation">:</span>    <span class="token key atrule">vgroup-mapping</span><span class="token punctuation">:</span> <span class="token comment" spellcheck="true"># 事务组与cluster的映射关系</span>      <span class="token key atrule">seata-demo</span><span class="token punctuation">:</span> SH<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="➃Seata实践"><a href="#➃Seata实践" class="headerlink" title="➃Seata实践"></a>➃Seata实践</h2><h3 id="❶XA模式"><a href="#❶XA模式" class="headerlink" title="❶XA模式"></a>❶XA模式</h3><p>XA 规范 是 X&#x2F;Open 组织定义的分布式事务处理（DTP，Distributed Transaction Processing）标准，XA 规范描述了全局的TM与局部的RM之间的接口，几乎所有主流的数据库都对 XA 规范提供了支持。</p><h4 id="1-两阶段提交"><a href="#1-两阶段提交" class="headerlink" title="1.两阶段提交"></a>1.两阶段提交</h4><p>XA是规范，目前主流数据库都实现了这种规范，实现的原理都是基于两阶段提交。</p><table><thead><tr><th>正常情况</th><th>异常情况</th></tr></thead><tbody><tr><td><img src="https://img.jwt1399.top/img/202210161435238.png"></td><td><img src="https://img.jwt1399.top/img/202210161435283.png"></td></tr></tbody></table><p>一阶段：</p><ul><li>事务协调者通知每个事物参与者执行本地事务</li><li>本地事务执行完成后报告事务执行状态给事务协调者，此时事务不提交，继续持有DB锁</li></ul><p>二阶段：</p><ul><li>事务协调者基于一阶段的报告来判断下一步操作<ul><li>如果一阶段都成功，则通知所有事务参与者，提交事务</li><li>如果一阶段任意一个参与者失败，则通知所有事务参与者回滚事务</li></ul></li></ul><h4 id="2-Seata的XA模型"><a href="#2-Seata的XA模型" class="headerlink" title="2.Seata的XA模型"></a>2.Seata的XA模型</h4><p>Seata对原始的XA模式做了简单的封装和改造，以适应自己的事务模型，基本架构如图：</p><p><img src="https://img.jwt1399.top/img/202210161438000.png"></p><p>RM一阶段的工作：</p><ul><li><p>a.注册分支事务到TC</p></li><li><p>b.执行分支业务sql但不提交</p></li><li><p>c.报告执行状态到TC</p></li></ul><p>TC二阶段的工作：TC检测各分支事务执行状态</p><ul><li><p>a.如果都成功，通知所有RM提交事务</p></li><li><p>b.如果有失败，通知所有RM回滚事务</p></li></ul><p>RM二阶段的工作：接收TC指令，提交或回滚事务</p><h4 id="3-优缺点"><a href="#3-优缺点" class="headerlink" title="3.优缺点"></a>3.优缺点</h4><p>XA模式的优点是什么？</p><ul><li>事务的强一致性，满足ACID原则。</li><li>常用数据库都支持，实现简单，并且没有代码侵入</li></ul><p>XA模式的缺点是什么？</p><ul><li>因为一阶段需要锁定数据库资源，等待二阶段结束才释放，性能较差</li><li>依赖关系型数据库实现事务</li></ul><h4 id="4-实现XA模式"><a href="#4-实现XA模式" class="headerlink" title="4.实现XA模式"></a>4.实现XA模式</h4><p>Seata的starter已经完成了XA模式的自动装配，实现非常简单，步骤如下：</p><p>1）修改application.yml文件（每个参与事务的微服务），开启XA模式：</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">seata</span><span class="token punctuation">:</span>  <span class="token key atrule">data-source-proxy-mode</span><span class="token punctuation">:</span> XA<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>2）给发起全局事务的入口方法添加<code>@GlobalTransactional</code>注解:</p><p>本例中是OrderServiceImpl中的create方法.</p><p><img src="https://img.jwt1399.top/img/202210161453759.png"></p><p>3）重启服务并测试</p><p>重启order-service，再次测试，发现无论怎样，三个微服务都能成功回滚。</p><h3 id="❷AT模式"><a href="#❷AT模式" class="headerlink" title="❷AT模式"></a>❷AT模式</h3><p>AT模式同样是分阶段提交的事务模型，不过缺弥补了XA模型中资源锁定周期过长的缺陷。</p><h4 id="1-Seata的AT模型"><a href="#1-Seata的AT模型" class="headerlink" title="1.Seata的AT模型"></a>1.Seata的AT模型</h4><p><img src="https://img.jwt1399.top/img/202210161454450.png" alt="基本流程图"></p><p>阶段一RM的工作：</p><ul><li>注册分支事务</li><li>记录undo-log（数据快照）</li><li>执行业务sql并提交</li><li>报告事务状态</li></ul><p>阶段二提交时RM的工作：删除undo-log即可</p><p>阶段二回滚时RM的工作：根据undo-log恢复数据到更新前</p><h4 id="2-流程梳理"><a href="#2-流程梳理" class="headerlink" title="2.流程梳理"></a>2.流程梳理</h4><p>我们用一个真实的业务来梳理下AT模式的原理。比如，现在有一个数据库表，记录用户余额：</p><table><thead><tr><th><strong>id</strong></th><th><strong>money</strong></th></tr></thead><tbody><tr><td>1</td><td>100</td></tr></tbody></table><p>其中一个分支业务要执行的SQL为：</p><pre class="line-numbers language-sql"><code class="language-sql"><span class="token keyword">update</span> tb_account <span class="token keyword">set</span> money <span class="token operator">=</span> money <span class="token operator">-</span> <span class="token number">10</span> <span class="token keyword">where</span> id <span class="token operator">=</span> <span class="token number">1</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>AT模式下，当前分支事务执行流程如下：</p><table><thead><tr><th>流程图</th><th>阶段</th></tr></thead><tbody><tr><td><img src="/../images/SpringCloud-%E9%AB%98%E7%BA%A7%E7%AF%87/image-20221016151005722.png"></td><td>阶段一：<br/>1.TM发起并注册全局事务到TC<br/>2.TM调用分支事务<br/>3.分支事务准备执行业务SQL<br/>4.RM拦截业务SQL，根据where条件查询原始数据，形成快照。<br/>5.RM执行业务SQL，提交本地事务，释放数据库锁。此时 <code>money = 90</code><br/>6.RM报告本地事务状态给TC<br/>阶段二：<br/>1.TM通知TC事务结束<br/>2.TC检查分支事务状态<br/>  a.如果都成功，则立即删除快照<br/>  b.如果有分支事务失败，需要回滚。读取快照数据（<code>&#123;&quot;id&quot;: 1, &quot;money&quot;: 100&#125;</code>），将快照恢复到数据库。此时数据库再次恢复为100</td></tr></tbody></table><h4 id="3-AT与XA的区别"><a href="#3-AT与XA的区别" class="headerlink" title="3.AT与XA的区别"></a>3.AT与XA的区别</h4><p>简述AT模式与XA模式最大的区别是什么？</p><ul><li>XA模式一阶段不提交事务，锁定资源；AT模式一阶段直接提交，不锁定资源。</li><li>XA模式依赖数据库机制实现回滚；AT模式利用数据快照实现数据回滚。</li><li>XA模式强一致；AT模式最终一致</li></ul><h4 id="4-脏写问题"><a href="#4-脏写问题" class="headerlink" title="4.脏写问题"></a>4.脏写问题</h4><p>在多线程并发访问AT模式的分布式事务时，有可能出现脏写问题，如图：</p><p><img src="https://img.jwt1399.top/img/202210161519740.png"></p><p>解决思路是引入全局锁概念。在释放DB锁之前，先拿到全局锁。避免同一时刻有另外一个事务来操作当前数据。</p><p><img src="https://img.jwt1399.top/img/202210161520005.png"></p><h4 id="5-优缺点"><a href="#5-优缺点" class="headerlink" title="5.优缺点"></a>5.优缺点</h4><p>AT模式的优点：</p><ul><li>一阶段完成直接提交事务，释放数据库资源，性能比较好</li><li>利用全局锁实现读写隔离</li><li>没有代码侵入，框架自动完成回滚和提交</li></ul><p>AT模式的缺点：</p><ul><li>两阶段之间属于软状态，属于最终一致</li><li>框架的快照功能会影响性能，但比XA模式要好很多</li></ul><h4 id="6-实现AT模式"><a href="#6-实现AT模式" class="headerlink" title="6.实现AT模式"></a>6.实现AT模式</h4><p>AT模式中的快照生成、回滚等动作都是由框架自动完成，没有任何代码侵入，因此实现非常简单。</p><p>只不过，AT模式需要一个表来记录全局锁、另一张表来记录数据快照undo_log。</p><p>1）导入数据库表，记录全局锁，即导入提供的Sql文件：seata-at.sql</p><ul><li><p>lock_table导入到TC服务关联的数据库，</p></li><li><p>undo_log表导入到微服务关联的数据库</p></li></ul><p>2）修改application.yml文件，将事务模式修改为AT模式即可：</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">seata</span><span class="token punctuation">:</span>  <span class="token key atrule">data-source-proxy-mode</span><span class="token punctuation">:</span> AT <span class="token comment" spellcheck="true"># 默认就是AT</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>3）重启服务并测试</p><h3 id="❸TCC模式"><a href="#❸TCC模式" class="headerlink" title="❸TCC模式"></a>❸TCC模式</h3><p>TCC模式与AT模式很相似，每阶段都是独立事务，不同的是TCC通过人工编码实现数据恢复。需要实现三个方法：</p><ul><li><p>Try：资源的检测和预留； </p></li><li><p>Confirm：完成资源操作业务；要求 Try 成功 Confirm 一定要能成功。</p></li><li><p>Cancel：预留资源释放，可以理解为try的反向操作。</p></li></ul><h4 id="1-Seata的TCC模型"><a href="#1-Seata的TCC模型" class="headerlink" title="1.Seata的TCC模型"></a>1.Seata的TCC模型</h4><p>Seata中的TCC模型依然延续之前的事务架构，如图：</p><p><img src="https://img.jwt1399.top/img/202210171355418.png"></p><h4 id="2-流程分析"><a href="#2-流程分析" class="headerlink" title="2.流程分析"></a>2.流程分析</h4><blockquote><p>举例，一个扣减用户余额的业务。假设账户A原来余额是100，需要余额扣减30元。</p></blockquote><p>初始余额：</p><p><img src="https://img.jwt1399.top/img/202210171401157.png"></p><ul><li><strong>阶段一（ Try ）</strong>：检查余额是否充足，如果充足则冻结金额增加30元，可用余额扣除30</li></ul><p><img src="https://img.jwt1399.top/img/202210171401269.png"></p><p>此时，总金额 &#x3D; 冻结金额 + 可用金额，数量依然是100不变。事务直接提交无需等待其它事务。</p><ul><li>**阶段二（Confirm)**：假如要提交（Confirm），则冻结金额扣减30</li></ul><p><img src="https://img.jwt1399.top/img/202210171401837.png"></p><p>此时，总金额 &#x3D; 冻结金额 + 可用金额 &#x3D; 0 + 70  &#x3D; 70元</p><ul><li><strong>阶段二（Canncel）</strong>：如果要回滚（Cancel），则冻结金额扣减30，可用余额增加30</li></ul><p><img src="https://img.jwt1399.top/img/202210171401319.png"></p><h4 id="3-优缺点-1"><a href="#3-优缺点-1" class="headerlink" title="3.优缺点"></a>3.优缺点</h4><p>TCC模式的每个阶段是做什么的？</p><ul><li>Try：资源检查和预留</li><li>Confirm：业务执行和提交</li><li>Cancel：预留资源的释放</li></ul><p>TCC的优点是什么？</p><ul><li>一阶段完成直接提交事务，释放数据库资源，性能好</li><li>相比AT模型，无需生成快照，无需使用全局锁，性能最强</li><li>不依赖数据库事务，而是依赖补偿操作，可以用于非事务型数据库</li></ul><p>TCC的缺点是什么？</p><ul><li>有代码侵入，需要人为编写try、Confirm和Cancel接口，太麻烦</li><li>软状态，事务是最终一致</li><li>需要考虑Confirm和Cancel的失败情况，做好幂等处理</li></ul><h4 id="4-事务悬挂和空回滚"><a href="#4-事务悬挂和空回滚" class="headerlink" title="4.事务悬挂和空回滚"></a>4.事务悬挂和空回滚</h4><h5 id="1）空回滚"><a href="#1）空回滚" class="headerlink" title="1）空回滚"></a>1）空回滚</h5><p>当某分支事务的try阶段<strong>阻塞</strong>时，可能导致全局事务超时而触发二阶段的cancel操作。在未执行try操作时先执行了cancel操作，这时cancel不能做回滚，就是<strong>空回滚</strong>。</p><p><img src="https://img.jwt1399.top/img/202210171406599.png"></p><p>执行cancel操作时，应当判断try是否已经执行，如果尚未执行，则应该空回滚。</p><h5 id="2）业务悬挂"><a href="#2）业务悬挂" class="headerlink" title="2）业务悬挂"></a>2）业务悬挂</h5><p>对于已经空回滚的业务，之前被阻塞的try操作恢复，继续执行try，就永远不可能confirm或cancel ，事务一直处于中间状态，这就是<strong>业务悬挂</strong>。</p><p>执行try操作时，应当判断cancel是否已经执行过了，如果已经执行，应当阻止空回滚后的try操作，避免悬挂</p><h4 id="5-实现TCC模式"><a href="#5-实现TCC模式" class="headerlink" title="5.实现TCC模式"></a>5.实现TCC模式</h4><p>解决空回滚和业务悬挂问题，必须要记录当前事务状态，是在try、还是cancel？</p><h5 id="1）思路分析"><a href="#1）思路分析" class="headerlink" title="1）思路分析"></a>1）思路分析</h5><p>定义一张表：</p><pre class="line-numbers language-sql"><code class="language-sql"><span class="token keyword">CREATE</span> <span class="token keyword">TABLE</span> <span class="token punctuation">`</span>account_freeze_tbl<span class="token punctuation">`</span> <span class="token punctuation">(</span>  <span class="token punctuation">`</span>xid<span class="token punctuation">`</span> <span class="token keyword">varchar</span><span class="token punctuation">(</span><span class="token number">128</span><span class="token punctuation">)</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span>  <span class="token punctuation">`</span>user_id<span class="token punctuation">`</span> <span class="token keyword">varchar</span><span class="token punctuation">(</span><span class="token number">255</span><span class="token punctuation">)</span> <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span> <span class="token keyword">COMMENT</span> <span class="token string">'用户id'</span><span class="token punctuation">,</span>  <span class="token punctuation">`</span>freeze_money<span class="token punctuation">`</span> <span class="token keyword">int</span><span class="token punctuation">(</span><span class="token number">11</span><span class="token punctuation">)</span> unsigned <span class="token keyword">DEFAULT</span> <span class="token string">'0'</span> <span class="token keyword">COMMENT</span> <span class="token string">'冻结金额'</span><span class="token punctuation">,</span>  <span class="token punctuation">`</span>state<span class="token punctuation">`</span> <span class="token keyword">int</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span> <span class="token keyword">COMMENT</span> <span class="token string">'事务状态，0:try，1:confirm，2:cancel'</span><span class="token punctuation">,</span>  <span class="token keyword">PRIMARY</span> <span class="token keyword">KEY</span> <span class="token punctuation">(</span><span class="token punctuation">`</span>xid<span class="token punctuation">`</span><span class="token punctuation">)</span> <span class="token keyword">USING</span> <span class="token keyword">BTREE</span><span class="token punctuation">)</span> <span class="token keyword">ENGINE</span><span class="token operator">=</span><span class="token keyword">InnoDB</span> <span class="token keyword">DEFAULT</span> <span class="token keyword">CHARSET</span><span class="token operator">=</span>utf8 ROW_FORMAT<span class="token operator">=</span>COMPACT<span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>其中：</p><ul><li>xid：是全局事务id</li><li>freeze_money：用来记录用户冻结金额</li><li>state：用来记录事务状态</li></ul><p>那此时，我们的业务开怎么做呢？</p><ul><li>Try业务：<ul><li>记录冻结金额和事务状态到account_freeze表</li><li>扣减account表可用金额</li></ul></li><li>Confirm业务<ul><li>根据xid删除account_freeze表的冻结记录</li></ul></li><li>Cancel业务<ul><li>修改account_freeze表，冻结金额为0，state为2</li><li>修改account表，恢复可用金额</li></ul></li><li>如何判断是否空回滚？<ul><li>cancel业务中，根据xid查询account_freeze，如果为null则说明try还没做，需要空回滚</li></ul></li><li>如何避免业务悬挂？<ul><li>try业务中，根据xid查询account_freeze ，如果已经存在则证明Cancel已经执行，拒绝执行try业务</li></ul></li></ul><p>接下来，我们改造account-service，利用TCC实现余额扣减功能。</p><h5 id="2）声明TCC接口"><a href="#2）声明TCC接口" class="headerlink" title="2）声明TCC接口"></a>2）声明TCC接口</h5><p>TCC的Try、Confirm、Cancel方法都需要在接口中基于注解来声明，</p><p>我们在account-service项目中的<code>cn.itcast.account.service</code>包中新建一个接口，声明TCC三个接口：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@LocalTCC</span><span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">AccountTCCService</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@TwoPhaseBusinessAction</span><span class="token punctuation">(</span>name <span class="token operator">=</span> <span class="token string">"deduct"</span><span class="token punctuation">,</span> commitMethod <span class="token operator">=</span> <span class="token string">"confirm"</span><span class="token punctuation">,</span> rollbackMethod <span class="token operator">=</span> <span class="token string">"cancel"</span><span class="token punctuation">)</span>    <span class="token keyword">void</span> <span class="token function">deduct</span><span class="token punctuation">(</span><span class="token annotation punctuation">@BusinessActionContextParameter</span><span class="token punctuation">(</span>paramName <span class="token operator">=</span> <span class="token string">"userId"</span><span class="token punctuation">)</span> String userId<span class="token punctuation">,</span>                <span class="token annotation punctuation">@BusinessActionContextParameter</span><span class="token punctuation">(</span>paramName <span class="token operator">=</span> <span class="token string">"money"</span><span class="token punctuation">)</span><span class="token keyword">int</span> money<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">boolean</span> <span class="token function">confirm</span><span class="token punctuation">(</span>BusinessActionContext ctx<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">boolean</span> <span class="token function">cancel</span><span class="token punctuation">(</span>BusinessActionContext ctx<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="3）编写实现类"><a href="#3）编写实现类" class="headerlink" title="3）编写实现类"></a>3）编写实现类</h4><p>在account-service服务中的<code>cn.itcast.account.service.impl</code>包下新建一个类，实现TCC业务：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Service</span><span class="token annotation punctuation">@Slf4j</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">AccountTCCServiceImpl</span> <span class="token keyword">implements</span> <span class="token class-name">AccountTCCService</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Autowired</span>    <span class="token keyword">private</span> AccountMapper accountMapper<span class="token punctuation">;</span>    <span class="token annotation punctuation">@Autowired</span>    <span class="token keyword">private</span> AccountFreezeMapper freezeMapper<span class="token punctuation">;</span>    <span class="token annotation punctuation">@Override</span>    <span class="token annotation punctuation">@Transactional</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">deduct</span><span class="token punctuation">(</span>String userId<span class="token punctuation">,</span> <span class="token keyword">int</span> money<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 0.获取事务id</span>        String xid <span class="token operator">=</span> RootContext<span class="token punctuation">.</span><span class="token function">getXID</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 1.扣减可用余额</span>        accountMapper<span class="token punctuation">.</span><span class="token function">deduct</span><span class="token punctuation">(</span>userId<span class="token punctuation">,</span> money<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 2.记录冻结金额，事务状态</span>        AccountFreeze freeze <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">AccountFreeze</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        freeze<span class="token punctuation">.</span><span class="token function">setUserId</span><span class="token punctuation">(</span>userId<span class="token punctuation">)</span><span class="token punctuation">;</span>        freeze<span class="token punctuation">.</span><span class="token function">setFreezeMoney</span><span class="token punctuation">(</span>money<span class="token punctuation">)</span><span class="token punctuation">;</span>        freeze<span class="token punctuation">.</span><span class="token function">setState</span><span class="token punctuation">(</span>AccountFreeze<span class="token punctuation">.</span>State<span class="token punctuation">.</span>TRY<span class="token punctuation">)</span><span class="token punctuation">;</span>        freeze<span class="token punctuation">.</span><span class="token function">setXid</span><span class="token punctuation">(</span>xid<span class="token punctuation">)</span><span class="token punctuation">;</span>        freezeMapper<span class="token punctuation">.</span><span class="token function">insert</span><span class="token punctuation">(</span>freeze<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> <span class="token keyword">boolean</span> <span class="token function">confirm</span><span class="token punctuation">(</span>BusinessActionContext ctx<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 1.获取事务id</span>        String xid <span class="token operator">=</span> ctx<span class="token punctuation">.</span><span class="token function">getXid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 2.根据id删除冻结记录</span>        <span class="token keyword">int</span> count <span class="token operator">=</span> freezeMapper<span class="token punctuation">.</span><span class="token function">deleteById</span><span class="token punctuation">(</span>xid<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> count <span class="token operator">==</span> <span class="token number">1</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> <span class="token keyword">boolean</span> <span class="token function">cancel</span><span class="token punctuation">(</span>BusinessActionContext ctx<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 0.查询冻结记录</span>        String xid <span class="token operator">=</span> ctx<span class="token punctuation">.</span><span class="token function">getXid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        AccountFreeze freeze <span class="token operator">=</span> freezeMapper<span class="token punctuation">.</span><span class="token function">selectById</span><span class="token punctuation">(</span>xid<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 1.恢复可用余额</span>        accountMapper<span class="token punctuation">.</span><span class="token function">refund</span><span class="token punctuation">(</span>freeze<span class="token punctuation">.</span><span class="token function">getUserId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> freeze<span class="token punctuation">.</span><span class="token function">getFreezeMoney</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 2.将冻结金额清零，状态改为CANCEL</span>        freeze<span class="token punctuation">.</span><span class="token function">setFreezeMoney</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        freeze<span class="token punctuation">.</span><span class="token function">setState</span><span class="token punctuation">(</span>AccountFreeze<span class="token punctuation">.</span>State<span class="token punctuation">.</span>CANCEL<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">int</span> count <span class="token operator">=</span> freezeMapper<span class="token punctuation">.</span><span class="token function">updateById</span><span class="token punctuation">(</span>freeze<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> count <span class="token operator">==</span> <span class="token number">1</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="❹SAGA模式"><a href="#❹SAGA模式" class="headerlink" title="❹SAGA模式"></a>❹SAGA模式</h3><p>Saga 模式是 Seata 即将开源的长事务解决方案，将由蚂蚁金服主要贡献。其理论基础是Hector &amp; Kenneth  在1987年发表的论文<a href="https://microservices.io/patterns/data/saga.html">Sagas</a>。</p><p>Seata官网对于Saga的指南：<a href="https://seata.io/zh-cn/docs/user/saga.html">https://seata.io/zh-cn/docs/user/saga.html</a></p><h4 id="1-原理"><a href="#1-原理" class="headerlink" title="1.原理"></a>1.原理</h4><p>在 Saga 模式下，分布式事务内有多个参与者，每一个参与者都是一个冲正补偿服务，需要用户根据业务场景实现其正向操作和逆向回滚操作。</p><p>分布式事务执行过程中，依次执行各参与者的正向操作，如果所有正向操作均执行成功，那么分布式事务提交。如果任何一个正向操作执行失败，那么分布式事务会去退回去执行前面各参与者的逆向回滚操作，回滚已提交的参与者，使分布式事务回到初始状态。</p><p><img src="/../images/SpringCloud-%E9%AB%98%E7%BA%A7%E7%AF%87/image-20210724184846396.png"></p><p>Saga也分为两个阶段：</p><ul><li>一阶段：直接提交本地事务</li><li>二阶段：成功则什么都不做；失败则通过编写补偿业务来回滚</li></ul><h4 id="2-优缺点"><a href="#2-优缺点" class="headerlink" title="2.优缺点"></a>2.优缺点</h4><p>优点：</p><ul><li>事务参与者可以基于事件驱动实现异步调用，吞吐高</li><li>一阶段直接提交事务，无锁，性能好</li><li>不用编写TCC中的三个阶段，实现简单</li></ul><p>缺点：</p><ul><li>软状态持续时间不确定，时效性差</li><li>没有锁，没有事务隔离，会有脏写</li></ul><h3 id="❺四种模式对比"><a href="#❺四种模式对比" class="headerlink" title="❺四种模式对比"></a>❺四种模式对比</h3><p>我们从以下几个方面来对比四种实现：</p><ul><li>一致性：能否保证事务的一致性？强一致还是最终一致？</li><li>隔离性：事务之间的隔离性如何？</li><li>代码侵入：是否需要对业务代码改造？</li><li>性能：有无性能损耗？</li><li>场景：常见的业务场景</li></ul><p><img src="https://img.jwt1399.top/img/202210161540817.png"></p><h2 id="➄高可用"><a href="#➄高可用" class="headerlink" title="➄高可用"></a>➄高可用</h2><h3 id="❶高可用架构模型"><a href="#❶高可用架构模型" class="headerlink" title="❶高可用架构模型"></a>❶高可用架构模型</h3><p>搭建TC服务集群非常简单，启动多个TC服务，注册到nacos即可。但集群并不能确保100%安全，万一集群所在机房故障怎么办？所以如果要求较高，一般都会做异地多机房容灾。比如一个TC集群在上海，另一个TC集群在杭州：</p><p><img src="/../images/SpringCloud-%E9%AB%98%E7%BA%A7%E7%AF%87/image-20221017141942983.png" alt="image-20221017141942983"></p><p>微服务基于事务组（tx-service-group)与TC集群的映射关系，来查找当前应该使用哪个TC集群。当SH集群故障时，只需要将vgroup-mapping中的映射关系改成HZ。则所有微服务就会切换到HZ的TC集群了。</p><h3 id="❷TC服务的高可用和异地容灾"><a href="#❷TC服务的高可用和异地容灾" class="headerlink" title="❷TC服务的高可用和异地容灾"></a>❷TC服务的高可用和异地容灾</h3><h4 id="1-模拟异地容灾的TC集群"><a href="#1-模拟异地容灾的TC集群" class="headerlink" title="1.模拟异地容灾的TC集群"></a>1.模拟异地容灾的TC集群</h4><p>计划启动两台seata的tc服务节点：</p><table><thead><tr><th>节点名称</th><th>ip地址</th><th>端口号</th><th>集群名称</th></tr></thead><tbody><tr><td>seata</td><td>127.0.0.1</td><td>8091</td><td>SH</td></tr><tr><td>seata2</td><td>127.0.0.1</td><td>8092</td><td>HZ</td></tr></tbody></table><p>之前我们已经启动了一台seata服务，端口是8091，集群名为SH。现在，将seata目录复制一份，起名为seata2</p><p>修改seata2&#x2F;conf&#x2F;registry.conf内容如下：</p><pre class="line-numbers language-nginx"><code class="language-nginx">registry <span class="token punctuation">{</span>  <span class="token comment" spellcheck="true"># tc服务的注册中心类，这里选择nacos，也可以是eureka、zookeeper等</span>  type <span class="token operator">=</span> <span class="token string">"nacos"</span>  nacos <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true"># seata tc 服务注册到 nacos的服务名称，可以自定义</span>    application <span class="token operator">=</span> <span class="token string">"seata-tc-server"</span>    serverAddr <span class="token operator">=</span> <span class="token string">"127.0.0.1:8848"</span>    group <span class="token operator">=</span> <span class="token string">"DEFAULT_GROUP"</span>    namespace <span class="token operator">=</span> <span class="token string">""</span>    cluster <span class="token operator">=</span> <span class="token string">"HZ"</span>    username <span class="token operator">=</span> <span class="token string">"nacos"</span>    password <span class="token operator">=</span> <span class="token string">"nacos"</span>  <span class="token punctuation">}</span><span class="token punctuation">}</span>config <span class="token punctuation">{</span>  <span class="token comment" spellcheck="true"># 读取tc服务端的配置文件的方式，这里是从nacos配置中心读取，这样如果tc是集群，可以共享配置</span>  type <span class="token operator">=</span> <span class="token string">"nacos"</span>  <span class="token comment" spellcheck="true"># 配置nacos地址等信息</span>  nacos <span class="token punctuation">{</span>    serverAddr <span class="token operator">=</span> <span class="token string">"127.0.0.1:8848"</span>    namespace <span class="token operator">=</span> <span class="token string">""</span>    group <span class="token operator">=</span> <span class="token string">"SEATA_GROUP"</span>    username <span class="token operator">=</span> <span class="token string">"nacos"</span>    password <span class="token operator">=</span> <span class="token string">"nacos"</span>    dataId <span class="token operator">=</span> <span class="token string">"seataServer.properties"</span>  <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>进入seata2&#x2F;bin目录，然后运行命令：</p><pre class="line-numbers language-powershell"><code class="language-powershell">seata<span class="token operator">-</span>server<span class="token punctuation">.</span>sh <span class="token operator">-</span>p 8092<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>打开nacos控制台，查看服务列表：</p><p><img src="/../images/SpringCloud-%E9%AB%98%E7%BA%A7%E7%AF%87/image-20210624151150840.png"></p><p>点进详情查看：</p><p><img src="/../images/SpringCloud-%E9%AB%98%E7%BA%A7%E7%AF%87/image-20210624151221747.png"></p><h4 id="2-将事务组映射配置到nacos"><a href="#2-将事务组映射配置到nacos" class="headerlink" title="2.将事务组映射配置到nacos"></a>2.将事务组映射配置到nacos</h4><p>接下来，我们需要将tx-service-group与cluster的映射关系都配置到nacos配置中心。</p><p>新建一个配置：</p><p><img src="/../images/SpringCloud-%E9%AB%98%E7%BA%A7%E7%AF%87/image-20210624151507072.png"></p><p>配置的内容如下：</p><pre class="line-numbers language-properties"><code class="language-properties"><span class="token comment" spellcheck="true"># 事务组映射关系</span><span class="token attr-name">service.vgroupMapping.seata-demo</span><span class="token punctuation">=</span><span class="token attr-value">SH</span><span class="token attr-name">service.enableDegrade</span><span class="token punctuation">=</span><span class="token attr-value">false</span><span class="token attr-name">service.disableGlobalTransaction</span><span class="token punctuation">=</span><span class="token attr-value">false</span><span class="token comment" spellcheck="true"># 与TC服务的通信配置</span><span class="token attr-name">transport.type</span><span class="token punctuation">=</span><span class="token attr-value">TCP</span><span class="token attr-name">transport.server</span><span class="token punctuation">=</span><span class="token attr-value">NIO</span><span class="token attr-name">transport.heartbeat</span><span class="token punctuation">=</span><span class="token attr-value">true</span><span class="token attr-name">transport.enableClientBatchSendRequest</span><span class="token punctuation">=</span><span class="token attr-value">false</span><span class="token attr-name">transport.threadFactory.bossThreadPrefix</span><span class="token punctuation">=</span><span class="token attr-value">NettyBoss</span><span class="token attr-name">transport.threadFactory.workerThreadPrefix</span><span class="token punctuation">=</span><span class="token attr-value">NettyServerNIOWorker</span><span class="token attr-name">transport.threadFactory.serverExecutorThreadPrefix</span><span class="token punctuation">=</span><span class="token attr-value">NettyServerBizHandler</span><span class="token attr-name">transport.threadFactory.shareBossWorker</span><span class="token punctuation">=</span><span class="token attr-value">false</span><span class="token attr-name">transport.threadFactory.clientSelectorThreadPrefix</span><span class="token punctuation">=</span><span class="token attr-value">NettyClientSelector</span><span class="token attr-name">transport.threadFactory.clientSelectorThreadSize</span><span class="token punctuation">=</span><span class="token attr-value">1</span><span class="token attr-name">transport.threadFactory.clientWorkerThreadPrefix</span><span class="token punctuation">=</span><span class="token attr-value">NettyClientWorkerThread</span><span class="token attr-name">transport.threadFactory.bossThreadSize</span><span class="token punctuation">=</span><span class="token attr-value">1</span><span class="token attr-name">transport.threadFactory.workerThreadSize</span><span class="token punctuation">=</span><span class="token attr-value">default</span><span class="token attr-name">transport.shutdown.wait</span><span class="token punctuation">=</span><span class="token attr-value">3</span><span class="token comment" spellcheck="true"># RM配置</span><span class="token attr-name">client.rm.asyncCommitBufferLimit</span><span class="token punctuation">=</span><span class="token attr-value">10000</span><span class="token attr-name">client.rm.lock.retryInterval</span><span class="token punctuation">=</span><span class="token attr-value">10</span><span class="token attr-name">client.rm.lock.retryTimes</span><span class="token punctuation">=</span><span class="token attr-value">30</span><span class="token attr-name">client.rm.lock.retryPolicyBranchRollbackOnConflict</span><span class="token punctuation">=</span><span class="token attr-value">true</span><span class="token attr-name">client.rm.reportRetryCount</span><span class="token punctuation">=</span><span class="token attr-value">5</span><span class="token attr-name">client.rm.tableMetaCheckEnable</span><span class="token punctuation">=</span><span class="token attr-value">false</span><span class="token attr-name">client.rm.tableMetaCheckerInterval</span><span class="token punctuation">=</span><span class="token attr-value">60000</span><span class="token attr-name">client.rm.sqlParserType</span><span class="token punctuation">=</span><span class="token attr-value">druid</span><span class="token attr-name">client.rm.reportSuccessEnable</span><span class="token punctuation">=</span><span class="token attr-value">false</span><span class="token attr-name">client.rm.sagaBranchRegisterEnable</span><span class="token punctuation">=</span><span class="token attr-value">false</span><span class="token comment" spellcheck="true"># TM配置</span><span class="token attr-name">client.tm.commitRetryCount</span><span class="token punctuation">=</span><span class="token attr-value">5</span><span class="token attr-name">client.tm.rollbackRetryCount</span><span class="token punctuation">=</span><span class="token attr-value">5</span><span class="token attr-name">client.tm.defaultGlobalTransactionTimeout</span><span class="token punctuation">=</span><span class="token attr-value">60000</span><span class="token attr-name">client.tm.degradeCheck</span><span class="token punctuation">=</span><span class="token attr-value">false</span><span class="token attr-name">client.tm.degradeCheckAllowTimes</span><span class="token punctuation">=</span><span class="token attr-value">10</span><span class="token attr-name">client.tm.degradeCheckPeriod</span><span class="token punctuation">=</span><span class="token attr-value">2000</span><span class="token comment" spellcheck="true"># undo日志配置</span><span class="token attr-name">client.undo.dataValidation</span><span class="token punctuation">=</span><span class="token attr-value">true</span><span class="token attr-name">client.undo.logSerialization</span><span class="token punctuation">=</span><span class="token attr-value">jackson</span><span class="token attr-name">client.undo.onlyCareUpdateColumns</span><span class="token punctuation">=</span><span class="token attr-value">true</span><span class="token attr-name">client.undo.logTable</span><span class="token punctuation">=</span><span class="token attr-value">undo_log</span><span class="token attr-name">client.undo.compress.enable</span><span class="token punctuation">=</span><span class="token attr-value">true</span><span class="token attr-name">client.undo.compress.type</span><span class="token punctuation">=</span><span class="token attr-value">zip</span><span class="token attr-name">client.undo.compress.threshold</span><span class="token punctuation">=</span><span class="token attr-value">64k</span><span class="token attr-name">client.log.exceptionRate</span><span class="token punctuation">=</span><span class="token attr-value">100</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="3-微服务读取nacos配置"><a href="#3-微服务读取nacos配置" class="headerlink" title="3.微服务读取nacos配置"></a>3.微服务读取nacos配置</h4><p>接下来，需要修改每一个微服务的application.yml文件，让微服务读取nacos中的client.properties文件：</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">seata</span><span class="token punctuation">:</span>  <span class="token key atrule">config</span><span class="token punctuation">:</span>    <span class="token key atrule">type</span><span class="token punctuation">:</span> nacos    <span class="token key atrule">nacos</span><span class="token punctuation">:</span>      <span class="token key atrule">server-addr</span><span class="token punctuation">:</span> 127.0.0.1<span class="token punctuation">:</span><span class="token number">8848</span>      <span class="token key atrule">username</span><span class="token punctuation">:</span> nacos      <span class="token key atrule">password</span><span class="token punctuation">:</span> nacos      <span class="token key atrule">group</span><span class="token punctuation">:</span> SEATA_GROUP      <span class="token key atrule">data-id</span><span class="token punctuation">:</span> client.properties<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>重启微服务，现在微服务到底是连接tc的SH集群，还是tc的HZ集群，都统一由nacos的client.properties来决定了。</p><h1 id="3-分布式缓存"><a href="#3-分布式缓存" class="headerlink" title="3.分布式缓存"></a>3.分布式缓存</h1><h2 id="①Redis持久化"><a href="#①Redis持久化" class="headerlink" title="①Redis持久化"></a>①Redis持久化</h2><h3 id="❶RDB持久化"><a href="#❶RDB持久化" class="headerlink" title="❶RDB持久化"></a>❶RDB持久化</h3><p>RDB全称Redis Database Backup file（Redis数据备份文件），也被叫做Redis数据快照。简单来说就是把内存中的所有数据都记录到磁盘中。当Redis实例故障重启后，从磁盘读取快照文件，恢复数据。快照文件称为RDB文件，默认是保存在当前运行目录。</p><h4 id="1-执行时机"><a href="#1-执行时机" class="headerlink" title="1.执行时机"></a>1.执行时机</h4><p>RDB持久化在四种情况下会执行：</p><ul><li>执行save命令</li><li>执行bgsave命令</li><li>Redis停机时</li><li>触发RDB条件时</li></ul><p><strong>1）save命令</strong></p><p>执行下面的命令，可以立即执行一次RDB：</p><p><img src="https://img.jwt1399.top/img/202210171526801.png"></p><p>save命令会导致<strong>主进程</strong>执行RDB，这个过程中其它所有命令都会被阻塞。只有在数据迁移时可能用到。</p><p><strong>2）bgsave命令</strong></p><p>下面的命令可以异步执行RDB：</p><p><img src="https://img.jwt1399.top/img/202210171527946.png"></p><p>这个命令执行后会开启<strong>子进程</strong>完成RDB，主进程可以持续处理用户请求，不受影响。</p><p><strong>3）停机时</strong></p><p>Redis停机时会执行一次save命令，实现RDB持久化。</p><p><strong>4）触发RDB条件</strong></p><p>Redis内部有触发RDB的机制，可以在redis.conf文件中找到，格式如下：</p><pre class="line-numbers language-properties"><code class="language-properties"><span class="token comment" spellcheck="true"># 900秒内，如果至少有1个key被修改，则执行bgsave ， 如果是save "" 则表示禁用RDB</span><span class="token attr-name">save</span> <span class="token attr-value">900 1  </span><span class="token attr-name">save</span> <span class="token attr-value">300 10  </span><span class="token attr-name">save</span> <span class="token attr-value">60 10000 </span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>RDB的其它配置也可以在redis.conf文件中设置：</p><pre class="line-numbers language-properties"><code class="language-properties"><span class="token comment" spellcheck="true"># 是否压缩 ,建议不开启，压缩也会消耗cpu，磁盘的话不值钱</span><span class="token attr-name">rdbcompression</span> <span class="token attr-value">yes</span><span class="token comment" spellcheck="true"># RDB文件名称</span><span class="token attr-name">dbfilename</span> <span class="token attr-value">dump.rdb  </span><span class="token comment" spellcheck="true"># 文件保存的路径目录</span><span class="token attr-name">dir</span> <span class="token attr-value">./ </span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="2-RDB原理"><a href="#2-RDB原理" class="headerlink" title="2.RDB原理"></a>2.RDB原理</h4><p>bgsave开始时会fork主进程得到子进程，子进程共享主进程的内存数据。完成fork后读取内存数据并写入 RDB 文件。fork采用的是copy-on-write技术：</p><ul><li>当主进程执行读操作时，访问共享内存；</li><li>当主进程执行写操作时，则会拷贝一份数据，执行写操作。</li></ul><p><img src="https://img.jwt1399.top/img/202210171530554.png"></p><h4 id="3-小结"><a href="#3-小结" class="headerlink" title="3.小结"></a>3.小结</h4><p>RDB方式bgsave的基本流程？</p><ul><li>fork主进程得到一个子进程，共享内存空间</li><li>子进程读取内存数据并写入新的RDB文件</li><li>用新RDB文件替换旧的RDB文件</li></ul><p>RDB会在什么时候执行？save 60 1000代表什么含义？</p><ul><li>默认是服务停止时</li><li>代表60秒内至少执行1000次修改则触发RDB</li></ul><p>RDB的缺点？</p><ul><li>RDB执行间隔时间长，两次RDB之间写入数据有丢失的风险</li><li>fork子进程、压缩、写出RDB文件都比较耗时</li></ul><h3 id="❷AOF持久化"><a href="#❷AOF持久化" class="headerlink" title="❷AOF持久化"></a>❷AOF持久化</h3><p>AOF全称为Append Only File（追加文件）。Redis处理的每一个写命令都会记录在AOF文件，可以看做是命令日志文件。</p><p><img src="https://img.jwt1399.top/img/202210171534071.png"></p><h4 id="1-AOF配置"><a href="#1-AOF配置" class="headerlink" title="1.AOF配置"></a>1.AOF配置</h4><p>AOF默认是关闭的，需要修改redis.conf配置文件来开启AOF：</p><pre class="line-numbers language-properties"><code class="language-properties"><span class="token comment" spellcheck="true"># 是否开启AOF功能，默认是no</span><span class="token attr-name">appendonly</span> <span class="token attr-value">yes</span><span class="token comment" spellcheck="true"># AOF文件的名称</span><span class="token attr-name">appendfilename</span> <span class="token attr-value">"appendonly.aof"</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>AOF的命令记录的频率也可以通过redis.conf文件来配：</p><pre class="line-numbers language-properties"><code class="language-properties"><span class="token comment" spellcheck="true"># 表示每执行一次写命令，立即记录到AOF文件</span><span class="token attr-name">appendfsync</span> <span class="token attr-value">always </span><span class="token comment" spellcheck="true"># 写命令执行完先放入AOF缓冲区，然后表示每隔1秒将缓冲区数据写到AOF文件，是默认方案</span><span class="token attr-name">appendfsync</span> <span class="token attr-value">everysec </span><span class="token comment" spellcheck="true"># 写命令执行完先放入AOF缓冲区，由操作系统决定何时将缓冲区内容写回磁盘</span><span class="token attr-name">appendfsync</span> <span class="token attr-value">no</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>三种策略对比：</p><p><img src="https://img.jwt1399.top/img/202210171535268.png"></p><h4 id="2-AOF文件重写"><a href="#2-AOF文件重写" class="headerlink" title="2.AOF文件重写"></a>2.AOF文件重写</h4><p>因为是记录命令，AOF文件会比RDB文件大的多。而且AOF会记录对同一个key的多次写操作，但只有最后一次写操作才有意义。通过执行bgrewriteaof命令，可以让AOF文件执行重写功能，用最少的命令达到相同效果。</p><p><img src="https://img.jwt1399.top/img/202210171537365.png"></p><p>如图，AOF原本有三个命令，但是<code>set num 123 和 set num 666</code>都是对num的操作，第二次会覆盖第一次的值，因此第一个命令记录下来没有意义。所以重写命令后，AOF文件内容就是：<code>mset name jack num 666</code></p><p>Redis也会在触发阈值时自动去重写AOF文件。阈值也可以在redis.conf中配置：</p><pre class="line-numbers language-properties"><code class="language-properties"><span class="token comment" spellcheck="true"># AOF文件比上次文件 增长超过多少百分比则触发重写</span><span class="token attr-name">auto-aof-rewrite-percentage</span> <span class="token attr-value">100</span><span class="token comment" spellcheck="true"># AOF文件体积最小多大以上才触发重写 </span><span class="token attr-name">auto-aof-rewrite-min-size</span> <span class="token attr-value">64mb </span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><h3 id="❸RDB与AOF对比"><a href="#❸RDB与AOF对比" class="headerlink" title="❸RDB与AOF对比"></a>❸RDB与AOF对比</h3><p>RDB和AOF各有自己的优缺点，如果对数据安全性要求较高，在实际开发中往往会<strong>结合</strong>两者来使用。</p><p><img src="https://img.jwt1399.top/img/202210171540950.png"></p><h2 id="②Redis主从"><a href="#②Redis主从" class="headerlink" title="②Redis主从"></a>②Redis主从</h2><h3 id="❶主从同步原理"><a href="#❶主从同步原理" class="headerlink" title="❶主从同步原理"></a>❶主从同步原理</h3><h4 id="1-全量同步"><a href="#1-全量同步" class="headerlink" title="1.全量同步"></a>1.全量同步</h4><p>主从第一次建立连接时，会执行<strong>全量同步</strong>，将master节点的所有数据都拷贝给slave节点，流程：</p><p><img src="https://img.jwt1399.top/img/202210242159182.png"></p><p>master如何得知salve是第一次来连接呢？？</p><p>有几个概念，可以作为判断依据：</p><ul><li><strong>Replication Id</strong>：简称replid，是数据集的标记，id一致则说明是同一数据集。每一个master都有唯一的replid，slave则会继承master节点的replid</li><li><strong>offset</strong>：偏移量，随着记录在repl_baklog中的数据增多而逐渐增大。slave完成同步时也会记录当前同步的offset。如果slave的offset小于master的offset，说明slave数据落后于master，需要更新。</li></ul><p>因此slave做数据同步，必须向master声明自己的replication id 和offset，master才可以判断到底需要同步哪些数据。因为slave原本也是一个master，有自己的replid和offset，当第一次变成slave，与master建立连接时，发送的replid和offset是自己的replid和offset。</p><p>master判断发现slave发送来的replid与自己的不一致，说明这是一个全新的slave，就知道要做全量同步了。</p><p>master会将自己的replid和offset都发送给这个slave，slave保存这些信息。以后slave的replid就与master一致了。</p><p>因此，<strong>master判断一个节点是否是第一次同步的依据，就是看replid是否一致</strong>。流程：</p><p><img src="https://img.jwt1399.top/img/202210242159471.png"></p><h4 id="2-增量同步"><a href="#2-增量同步" class="headerlink" title="2.增量同步"></a>2.增量同步</h4><p>全量同步需要先做RDB，然后将RDB文件通过网络传输个slave，成本太高了。因此除了第一次做全量同步，其它大多数时候slave与master都是做<strong>增量同步</strong>。增量同步就是只更新slave与master存在差异的部分数据。流程：</p><p><img src="https://img.jwt1399.top/img/202210242206846.png"></p><p>那么master怎么知道slave与自己的数据差异在哪里呢?</p><h4 id="3-repl-backlog原理"><a href="#3-repl-backlog原理" class="headerlink" title="3.repl_backlog原理"></a>3.repl_backlog原理</h4><blockquote><p>master怎么知道slave与自己的数据差异在哪里呢？这就要说到全量同步时的repl_baklog文件了。</p></blockquote><p>这个文件是一个固定大小的<strong>环形数组</strong>，也就是说<strong>角标到达数组末尾后，会再次从0开始读写</strong>，这样数组头部的数据就会被覆盖。repl_baklog中会记录Redis处理过的命令日志及offset，包括master当前的offset，和slave已经拷贝到的offset。</p><p>➀slave与master的offset之间的差异，就是salve需要增量拷贝的数据了(即图中红色部分)。</p><p>➁随着不断有数据写入，master的offset逐渐变大，slave也不断的拷贝，追赶master的offset</p><p>➂直到数组被填满，此时，如果有新的数据写入，就会覆盖数组中的旧数据。不过，旧的数据只要是绿色的，说明是已经被同步到slave的数据，即便被覆盖了也没什么影响。因为未同步的是红色部分。</p><p>➃但是，如果slave出现网络阻塞，导致master的offset远远超过了slave的offset，如果master继续写入新数据，其offset就会覆盖旧的数据，直到将slave现在的offset也覆盖</p><p>➄棕色框中的红色部分，就是尚未同步，但是却已经被覆盖的数据。此时如果slave恢复，需要同步，却发现自己的offset都没有了，无法完成增量同步了。只能做全量同步。</p><table><thead><tr><th>➀</th><th>➁</th><th>➂</th><th>➃</th><th>➄</th></tr></thead><tbody><tr><td><img src="https://img.jwt1399.top/img/202210242220885.png"></td><td><img src="https://img.jwt1399.top/img/202210242220139.png"></td><td><img src="https://img.jwt1399.top/img/202210242220579.png"></td><td><img src="https://img.jwt1399.top/img/202210242220746.png"></td><td><img src="https://img.jwt1399.top/img/202210242220273.png"></td></tr></tbody></table><p>总结：repl_baklog大小有上限，写满后会覆盖最早的数据。如果slave断开时间过久，导致尚未备份的数据被覆盖，则无法基于1og做增量同步，只能再次全量同步。</p><h3 id="❷主从同步优化"><a href="#❷主从同步优化" class="headerlink" title="❷主从同步优化"></a>❷主从同步优化</h3><p>主从同步可以保证主从数据的一致性，可以从以下几个方面来优化Redis主从集群：</p><ul><li>在master中配置<code>repl-diskless-sync yes</code>启用无磁盘复制，避免全量同步时的磁盘IO。</li><li>Redis单节点上的内存占用不要太大，减少RDB导致的过多磁盘IO</li><li>适当提高repl_baklog的大小，发现slave宕机时尽快实现故障恢复，尽可能避免全量同步</li><li>限制一个master上的slave节点数量，如果实在是太多slave，则可以采用主-从-从链式结构，减少master压力</li></ul><p>主从从架构图：</p><p><img src="https://img.jwt1399.top/img/202210242223210.png"></p><h3 id="❸总结"><a href="#❸总结" class="headerlink" title="❸总结"></a>❸总结</h3><p>简述全量同步和增量同步区别？</p><ul><li>全量同步：master将完整内存数据生成RDB，发送RDB到slave。后续命令则记录在repl_baklog，逐个发送给slave。</li><li>增量同步：slave提交自己的offset到master，master获取repl_baklog中从offset之后的命令给slave</li></ul><p>什么时候执行全量同步？</p><ul><li>slave节点第一次连接master节点时</li><li>slave节点断开时间太久，repl_baklog中的offset已经被覆盖时</li></ul><p>什么时候执行增量同步？</p><ul><li>slave节点断开又恢复，并且在repl_baklog中能找到offset时</li></ul><h2 id="③Redis哨兵"><a href="#③Redis哨兵" class="headerlink" title="③Redis哨兵"></a>③Redis哨兵</h2><h3 id="❶哨兵结构和作用"><a href="#❶哨兵结构和作用" class="headerlink" title="❶哨兵结构和作用"></a>❶哨兵结构和作用</h3><p>Redis提供了哨兵（Sentinel）机制来实现主从集群的自动故障恢复。</p><p><img src="https://img.jwt1399.top/img/202210242243769.png" alt="哨兵的结构"></p><p>哨兵的作用如下：</p><ul><li><strong>监控</strong>：Sentinel会不断检查您的master和slave是否按预期工作</li><li><strong>自动故障恢复</strong>：如果master故障，Sentinel会将一个slave提升为master。故障实例恢复后也以新的master为主</li><li><strong>通知</strong>：Sentinel充当Redis客户端的服务发现来源，当集群发生故障转移时，将最新信息推送给Redis的客户端</li></ul><h3 id="❷集群监控原理"><a href="#❷集群监控原理" class="headerlink" title="❷集群监控原理"></a>❷集群监控原理</h3><p>Sentinel基于心跳机制监测服务状态，每隔1秒向集群的每个实例发送ping命令：</p><ul><li><p>主观下线：如果某sentinel节点发现某实例未在规定时间响应，则认为该实例<strong>主观下线</strong>。</p></li><li><p>客观下线：若超过指定数量（quorum）的sentinel都认为该实例主观下线，则该实例<strong>客观下线</strong>。quorum值最好超过Sentinel实例数量的一半。</p></li></ul><h3 id="❸集群故障恢复原理"><a href="#❸集群故障恢复原理" class="headerlink" title="❸集群故障恢复原理"></a>❸集群故障恢复原理</h3><p>一旦发现master故障，sentinel需要在salve中选择一个作为新的master，选择依据是这样的：</p><ul><li>首先会判断slave节点与master节点断开时间长短，如果超过指定值（down-after-milliseconds * 10）则会排除该slave节点</li><li>然后判断slave节点的slave-priority值，越小优先级越高，如果是0则永不参与选举</li><li>如果slave-prority一样，则判断slave节点的offset值，越大说明数据越新，优先级越高</li><li>最后是判断slave节点的运行id大小，越小优先级越高。</li></ul><p>当选出一个新的master后，该如何实现切换呢？流程如下：</p><ul><li>sentinel给备选的slave节点发送<code>slaveof no one</code>命令，让该节点成为master</li><li>sentinel给所有其它slave发送<code>slaveof 192.168.150.101 7002</code>命令（备选slave的地址和端口） ，让这些slave成为新master的从节点，开始从新的master上同步数据。</li><li>最后，sentinel将故障节点标记为slave，当故障节点恢复后会自动成为新的master的slave节点</li></ul><p><img src="https://img.jwt1399.top/img/202210242247262.png"></p><h3 id="❹总结"><a href="#❹总结" class="headerlink" title="❹总结"></a>❹总结</h3><p>Sentinel的三个作用是什么？</p><ul><li>监控</li><li>故障转移</li><li>通知</li></ul><p>Sentinel如何判断一个redis实例是否健康？</p><ul><li>每隔1秒发送一次ping命令，如果超过一定时间没有响应则认为是主观下线</li><li>如果大多数sentinel都认为实例主观下线，则判定服务下线</li></ul><p>故障转移步骤有哪些？</p><ul><li>首先选定一个slave作为新的master，执行slaveof no one</li><li>然后让所有节点都执行slaveof 新master</li><li>修改故障节点配置，添加slaveof 新master</li></ul><h2 id="④Redis分片集群"><a href="#④Redis分片集群" class="headerlink" title="④Redis分片集群"></a>④Redis分片集群</h2><h3 id="❶分片集群结构"><a href="#❶分片集群结构" class="headerlink" title="❶分片集群结构"></a>❶分片集群结构</h3><p>主从和哨兵可以解决高可用、高并发读的问题。但是依然有两个问题没有解决：</p><ul><li><p>海量数据存储问题</p></li><li><p>高并发写的问题</p></li></ul><p>使用分片集群可以解决上述问题，如图:</p><img src="https://img.jwt1399.top/img/202210242253041.png" style="zoom:50%;" /><p>分片集群特征：</p><ul><li><p>集群中有多个master，每个master保存不同数据</p></li><li><p>每个master都可以有多个slave节点</p></li><li><p>master之间通过ping监测彼此健康状态</p></li><li><p>客户端请求可以访问集群任意节点，最终都会被转发到正确节点</p></li></ul><h3 id="❷散列插槽"><a href="#❷散列插槽" class="headerlink" title="❷散列插槽"></a>❷散列插槽</h3><p>Redis会把每一个master节点映射到0~16383共16384个插槽（hash slot）上，查看集群信息时(<code>cluster nodes</code>)就能看到：</p><p><img src="https://img.jwt1399.top/img/202210242259764.png"></p><p>数据key不是与节点绑定，而是与插槽绑定。redis会根据key的有效部分计算插槽值，分两种情况：</p><ul><li>key中包含”{}”，且“{}”中至少包含1个字符，“{}”中的部分是有效部分</li><li>key中不包含“{}”，整个key都是有效部分</li></ul><p>例如：key是num，那么就根据num计算，如果是{jianjian}num，则根据jianjian计算。计算方式是利用CRC16算法得到一个hash值，然后对16384取余（CRC16(key) % 16384 ），得到的结果就是slot值。</p><p><img src="https://img.jwt1399.top/img/202210242304617.png"> </p><p>如图在7001这个节点执行set a 1时，对a做hash运算，对16384取余，得到的结果15495，因此要存储到7003节点。到了7003后，执行<code>get num</code>时，对num做hash运算，对16384取余，得到的结果2765，因此需要切换到7001节点</p><p><strong>总结</strong></p><p>Redis如何判断某个key应该在哪个实例？</p><ul><li>将16384个插槽分配到不同的实例</li><li>根据key的有效部分计算哈希值，对16384取余</li><li>余数作为插槽，寻找插槽所在实例即可</li></ul><p>如何将同一类数据固定的保存在同一个Redis实例？</p><ul><li>这一类数据使用相同的有效部分，例如key都以{typeId}为前缀</li></ul><h3 id="❸集群伸缩"><a href="#❸集群伸缩" class="headerlink" title="❸集群伸缩"></a>❸集群伸缩</h3><p>redis-cli –cluster提供了很多操作集群的命令，可以通过下面方式查看：</p><p><img src="https://img.jwt1399.top/img/202210242319401.png"></p><h4 id="1-需求分析"><a href="#1-需求分析" class="headerlink" title="1.需求分析"></a>1.需求分析</h4><p>需求：向集群中添加一个新的master节点，并向其中存储 num &#x3D; 10</p><ul><li>启动一个新的redis实例，端口为7004</li><li>添加7004到之前的集群，并作为一个master节点</li><li>给7004节点分配插槽，使得num这个key可以存储到7004实例</li></ul><p>这里需要两个新的功能：</p><ul><li>添加一个节点到集群中</li><li>将部分插槽分配到新插槽</li></ul><h4 id="2-创建新的redis实例"><a href="#2-创建新的redis实例" class="headerlink" title="2.创建新的redis实例"></a>2.创建新的redis实例</h4><p>创建一个文件夹：</p><pre class="line-numbers language-sh"><code class="language-sh">mkdir 7004<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>拷贝配置文件：</p><pre class="line-numbers language-sh"><code class="language-sh">cp redis.conf /7004<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>修改配置文件：</p><pre class="line-numbers language-sh"><code class="language-sh">sed /s/6379/7004/g 7004/redis.conf<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>启动</p><pre class="line-numbers language-sh"><code class="language-sh">redis-server 7004/redis.conf<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><h4 id="3-添加新节点到redis"><a href="#3-添加新节点到redis" class="headerlink" title="3.添加新节点到redis"></a>3.添加新节点到redis</h4><p>添加节点的语法如下：</p><p><img src="https://img.jwt1399.top/img/202210242319022.png"></p><p>执行命令：</p><pre class="line-numbers language-sh"><code class="language-sh">redis-cli --cluster add-node  192.168.150.101:7004 192.168.150.101:7001<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>通过命令查看集群状态：</p><pre class="line-numbers language-sh"><code class="language-sh">redis-cli -p 7001 cluster nodes<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>如图，7004加入了集群，并且默认是一个master节点：</p><p><img src="https://img.jwt1399.top/img/202210242319028.png"></p><p>但是，可以看到7004节点的插槽数量为0，因此没有任何数据可以存储到7004上</p><h4 id="4-转移插槽"><a href="#4-转移插槽" class="headerlink" title="4.转移插槽"></a>4.转移插槽</h4><p>我们要将num存储到7004节点，因此需要先看看num的插槽是多少：</p><p><img src="https://img.jwt1399.top/img/202210242320500.png"></p><p>如上图所示，num的插槽为2765.</p><p>我们可以将0~3000的插槽从7001转移到7004，命令格式如下：</p><p><img src="https://img.jwt1399.top/img/202210242320657.png"></p><p>具体命令如下：</p><p>建立连接：</p><p><img src="https://img.jwt1399.top/img/202210242320294.png"></p><p>得到下面的反馈：</p><p><img src="https://img.jwt1399.top/img/202210242320458.png"></p><p>询问要移动多少个插槽，我们计划是3000个：</p><p>新的问题来了：</p><p><img src="https://img.jwt1399.top/img/202210242321392.png"></p><p>那个node来接收这些插槽？？</p><p>显然是7004，那么7004节点的id是多少呢？</p><p><img src="https://img.jwt1399.top/img/202210242321252.png"></p><p>复制这个id，然后拷贝到刚才的控制台后：</p><p><img src="https://img.jwt1399.top/img/202210242321161.png"></p><p>这里询问，你的插槽是从哪里移动过来的？</p><ul><li>all：代表全部，也就是三个节点各转移一部分</li><li>具体的id：目标节点的id</li><li>done：没有了</li></ul><p>这里我们要从7001获取，因此填写7001的id：</p><p><img src="https://img.jwt1399.top/img/202210242321637.png"></p><p>填完后，点击done，这样插槽转移就准备好了：</p><p><img src="https://img.jwt1399.top/img/202210242322964.png"></p><p>确认要转移吗？输入yes：</p><p>然后，通过命令查看结果：</p><p><img src="https://img.jwt1399.top/img/202210242322787.png"> </p><p>可以看到： </p><p><img src="https://img.jwt1399.top/img/202210242322766.png"></p><p>目的达成。</p><h3 id="❹故障转移"><a href="#❹故障转移" class="headerlink" title="❹故障转移"></a>❹故障转移</h3><p>集群初识状态是这样的：</p><p><img src="https://img.jwt1399.top/img/202210242313025.png"></p><p>其中7001、7002、7003都是master，我们计划让7002宕机。</p><h4 id="1-自动故障转移"><a href="#1-自动故障转移" class="headerlink" title="1.自动故障转移"></a>1.自动故障转移</h4><p>当集群中有一个master宕机会发生什么呢？直接停止一个redis实例，例如7002：</p><pre class="line-numbers language-sh"><code class="language-sh">redis-cli -p 7002 shutdown<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>1）首先是该实例与其它实例失去连接，然后是疑似宕机：</p><p><img src="https://img.jwt1399.top/img/202210242313432.png"></p><p>2）最后是确定下线，自动提升一个slave为新的master：</p><p><img src="https://img.jwt1399.top/img/202210242313025.png"></p><p>3）当7002再次启动，就会变为一个slave节点了：</p><p><img src="https://img.jwt1399.top/img/202210242313079.png"></p><h4 id="2-手动故障转移"><a href="#2-手动故障转移" class="headerlink" title="2.手动故障转移"></a>2.手动故障转移</h4><p>利用cluster failover命令可以手动让集群中的某个master宕机，切换到执行cluster failover命令的这个slave节点，实现无感知的数据迁移。其流程如下：</p><img src="https://img.jwt1399.top/img/202210242314920.png" style="zoom:50%;" /><p>这种failover命令可以指定三种模式：</p><ul><li>缺省：默认的流程，如图1~6歩</li><li>force：省略了对offset的一致性校验</li><li>takeover：直接执行第5歩，忽略数据一致性、忽略master状态和其它master的意见</li></ul><p><strong>案例需求</strong>：在7002这个slave节点执行手动故障转移，重新夺回master地位</p><ul><li><p>1）利用redis-cli连接7002这个节点</p></li><li><p>2）执行cluster failover命令</p></li></ul><p>如图：</p><p><img src="https://img.jwt1399.top/img/202210242314826.png"></p><p>效果：</p><p><img src="https://img.jwt1399.top/img/202210242315932.png"></p><h1 id="4-多级缓存"><a href="#4-多级缓存" class="headerlink" title="4.多级缓存"></a>4.多级缓存</h1><h2 id="①什么是多级缓存"><a href="#①什么是多级缓存" class="headerlink" title="①什么是多级缓存"></a>①什么是多级缓存</h2><p><strong>传统缓存策略</strong>一般是请求到达Tomcat后，先查询Redis，如果未命中则查询数据库，存在下面的问题：</p><ul><li><p>请求要经过Tomcat处理，Tomcat的性能成为整个系统的瓶颈</p></li><li><p>Redis缓存失效时，会对数据库产生冲击</p></li></ul><p><strong>多级缓存策略</strong>就是充分利用请求处理的每个环节，分别添加缓存，减轻Tomcat压力，提升服务性能：</p><ul><li>浏览器访问静态资源时，优先读取浏览器本地缓存</li><li>访问非静态资源（ajax查询数据）时，访问服务端</li><li>请求到达Nginx后，优先读取Nginx本地缓存</li><li>如果Nginx本地缓存未命中，则去直接查询Redis（不经过Tomcat）</li><li>如果Redis查询未命中，则查询Tomcat</li><li>请求进入Tomcat后，优先查询JVM进程缓存</li><li>如果JVM进程缓存未命中，则查询数据库</li></ul><table><thead><tr><th align="center">传统缓存</th><th align="center">多级缓存</th></tr></thead><tbody><tr><td align="center"><img src="https://img.jwt1399.top/img/202210291820067.png"></td><td align="center"><img src="https://img.jwt1399.top/img/202210291820697.png"></td></tr></tbody></table><p>在多级缓存架构中，Nginx内部需要编写本地缓存查询、Redis查询、Tomcat查询的业务逻辑，因此这样的nginx服务不再是一个<strong>反向代理服务器</strong>，而是一个编写<strong>业务的Web服务器了</strong>。因此这样的业务Nginx也需要搭建集群来提高并发，再有专门的nginx服务来做反向代理，另外，我们的Tomcat服务将来也会部署为集群模式：</p><p><img src="https://img.jwt1399.top/img/202210291819191.png"></p><p>可见，多级缓存的关键有两个：</p><ul><li><p>1.在nginx中编写业务，实现nginx本地缓存、Redis、Tomcat的查询。利用OpenResty框架结合Lua实现</p></li><li><p>2.在Tomcat中实现JVM进程缓存，利用Caffeine框架来实现</p></li></ul><h2 id="②JVM进程缓存"><a href="#②JVM进程缓存" class="headerlink" title="②JVM进程缓存"></a>②JVM进程缓存</h2><h3 id="❶初识Caffeine"><a href="#❶初识Caffeine" class="headerlink" title="❶初识Caffeine"></a>❶初识Caffeine</h3><p>缓存在日常开发中启动至关重要的作用，由于是存储在内存中，数据的读取速度是非常快的，能大量减少对数据库的访问，减少数据库的压力。我们把缓存分为两类：</p><ul><li>分布式缓存，例如Redis：<ul><li>优点：存储容量更大、可靠性更好、可以在集群间共享</li><li>缺点：访问缓存有网络开销</li><li>场景：缓存数据量较大、可靠性要求较高、需要在集群间共享</li></ul></li><li>进程本地缓存，例如HashMap、GuavaCache：<ul><li>优点：读取本地内存，没有网络开销，速度更快</li><li>缺点：存储容量有限、可靠性较低、无法共享</li><li>场景：性能要求较高，缓存数据量较小</li></ul></li></ul><p>我们利用Caffeine框架来实现JVM进程缓存。<strong>Caffeine</strong>是一个基于Java8开发的，提供了近乎最佳命中率的高性能的本地缓存库。目前Spring内部的缓存使用的就是Caffeine。GitHub地址：<a href="https://github.com/ben-manes/caffeine">https://github.com/ben-manes/caffeine</a></p><p>Caffeine的基本API：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Test</span><span class="token keyword">void</span> <span class="token function">testBasicOps</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 构建cache对象</span>    Cache<span class="token operator">&lt;</span>String<span class="token punctuation">,</span> String<span class="token operator">></span> cache <span class="token operator">=</span> Caffeine<span class="token punctuation">.</span><span class="token function">newBuilder</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 存数据</span>    cache<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token string">"gf"</span><span class="token punctuation">,</span> <span class="token string">"迪丽热巴"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 取数据</span>    String gf <span class="token operator">=</span> cache<span class="token punctuation">.</span><span class="token function">getIfPresent</span><span class="token punctuation">(</span><span class="token string">"gf"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"gf = "</span> <span class="token operator">+</span> gf<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 取数据，包含两个参数：</span>    <span class="token comment" spellcheck="true">// 参数一：缓存的key</span>    <span class="token comment" spellcheck="true">// 参数二：Lambda表达式，表达式参数就是缓存的key，方法体是查询数据库的逻辑</span>    <span class="token comment" spellcheck="true">// 优先根据key查询JVM缓存，如果未命中，则执行参数二的Lambda表达式</span>    String defaultGF <span class="token operator">=</span> cache<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">"defaultGF"</span><span class="token punctuation">,</span> key <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 根据key去数据库查询数据</span>        <span class="token keyword">return</span> <span class="token string">"柳岩"</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"defaultGF = "</span> <span class="token operator">+</span> defaultGF<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>Caffeine需要有缓存的清除策略，不然的话内存总会有耗尽的时候。Caffeine提供了三种缓存驱逐策略：</p><ul><li><p><strong>基于容量</strong>：设置缓存的数量上限</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// 创建缓存对象</span>Cache<span class="token operator">&lt;</span>String<span class="token punctuation">,</span> String<span class="token operator">></span> cache <span class="token operator">=</span> Caffeine<span class="token punctuation">.</span><span class="token function">newBuilder</span><span class="token punctuation">(</span><span class="token punctuation">)</span>    <span class="token punctuation">.</span><span class="token function">maximumSize</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// 设置缓存大小上限为 1</span>    <span class="token punctuation">.</span><span class="token function">build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre></li><li><p><strong>基于时间</strong>：设置缓存的有效时间</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// 创建缓存对象</span>Cache<span class="token operator">&lt;</span>String<span class="token punctuation">,</span> String<span class="token operator">></span> cache <span class="token operator">=</span> Caffeine<span class="token punctuation">.</span><span class="token function">newBuilder</span><span class="token punctuation">(</span><span class="token punctuation">)</span>    <span class="token comment" spellcheck="true">// 设置缓存有效期为 10 秒，从最后一次写入开始计时 </span>    <span class="token punctuation">.</span><span class="token function">expireAfterWrite</span><span class="token punctuation">(</span>Duration<span class="token punctuation">.</span><span class="token function">ofSeconds</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">)</span>     <span class="token punctuation">.</span><span class="token function">build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p><strong>基于引用</strong>：设置缓存为软引用或弱引用，利用GC来回收缓存数据。性能较差，不建议使用。</p></li></ul><blockquote><p><strong>注意</strong>：在默认情况下，当一个缓存元素过期的时候，Caffeine不会自动立即将其清理和驱逐。而是在一次读或写操作后，或者在空闲时间完成对失效数据的驱逐。</p></blockquote><h3 id="❷实现JVM进程缓存"><a href="#❷实现JVM进程缓存" class="headerlink" title="❷实现JVM进程缓存"></a>❷实现JVM进程缓存</h3><h4 id="1-需求"><a href="#1-需求" class="headerlink" title="1.需求"></a>1.需求</h4><p>利用Caffeine实现下列需求：</p><ul><li>根据id查询商品的业务添加缓存，缓存未命中时查询数据库</li><li>根据id查询库存的业务添加缓存，缓存未命中时查询数据库</li><li>缓存初始大小为100</li><li>缓存上限为10000</li></ul><h4 id="2-实现"><a href="#2-实现" class="headerlink" title="2.实现"></a>2.实现</h4><p>首先，我们需要定义两个Caffeine的缓存对象，分别保存商品、库存的缓存数据。</p><p>在item-service的<code>com.heima.item.config</code>包下定义<code>CaffeineConfig</code>类：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">package</span> com<span class="token punctuation">.</span>heima<span class="token punctuation">.</span>item<span class="token punctuation">.</span>config<span class="token annotation punctuation">@Configuration</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">CaffeineConfig</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Bean</span>    <span class="token keyword">public</span> Cache<span class="token operator">&lt;</span>Long<span class="token punctuation">,</span> Item<span class="token operator">></span> <span class="token function">itemCache</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>        <span class="token keyword">return</span> Caffeine<span class="token punctuation">.</span><span class="token function">newBuilder</span><span class="token punctuation">(</span><span class="token punctuation">)</span>                <span class="token punctuation">.</span><span class="token function">initialCapacity</span><span class="token punctuation">(</span><span class="token number">100</span><span class="token punctuation">)</span>                <span class="token punctuation">.</span><span class="token function">maximumSize</span><span class="token punctuation">(</span>10_000<span class="token punctuation">)</span>                <span class="token punctuation">.</span><span class="token function">build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@Bean</span>    <span class="token keyword">public</span> Cache<span class="token operator">&lt;</span>Long<span class="token punctuation">,</span> ItemStock<span class="token operator">></span> <span class="token function">stockCache</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>        <span class="token keyword">return</span> Caffeine<span class="token punctuation">.</span><span class="token function">newBuilder</span><span class="token punctuation">(</span><span class="token punctuation">)</span>                <span class="token punctuation">.</span><span class="token function">initialCapacity</span><span class="token punctuation">(</span><span class="token number">100</span><span class="token punctuation">)</span>                <span class="token punctuation">.</span><span class="token function">maximumSize</span><span class="token punctuation">(</span>10_000<span class="token punctuation">)</span>                <span class="token punctuation">.</span><span class="token function">build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>然后，修改item-service中的<code>com.heima.item.web</code>包下的ItemController类，添加缓存逻辑：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@RestController</span><span class="token annotation punctuation">@RequestMapping</span><span class="token punctuation">(</span><span class="token string">"item"</span><span class="token punctuation">)</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ItemController</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Autowired</span>    <span class="token keyword">private</span> IItemService itemService<span class="token punctuation">;</span>    <span class="token annotation punctuation">@Autowired</span>    <span class="token keyword">private</span> IItemStockService stockService<span class="token punctuation">;</span>    <span class="token annotation punctuation">@Autowired</span>    <span class="token keyword">private</span> Cache<span class="token operator">&lt;</span>Long<span class="token punctuation">,</span> Item<span class="token operator">></span> itemCache<span class="token punctuation">;</span>    <span class="token annotation punctuation">@Autowired</span>    <span class="token keyword">private</span> Cache<span class="token operator">&lt;</span>Long<span class="token punctuation">,</span> ItemStock<span class="token operator">></span> stockCache<span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// ...其它略</span>        <span class="token annotation punctuation">@GetMapping</span><span class="token punctuation">(</span><span class="token string">"/{id}"</span><span class="token punctuation">)</span>    <span class="token keyword">public</span> Item <span class="token function">findById</span><span class="token punctuation">(</span><span class="token annotation punctuation">@PathVariable</span><span class="token punctuation">(</span><span class="token string">"id"</span><span class="token punctuation">)</span> Long id<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">//根据id查询商品的业务添加缓存，缓存未命中时查询数据库</span>        <span class="token keyword">return</span> itemCache<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>id<span class="token punctuation">,</span> key <span class="token operator">-</span><span class="token operator">></span> itemService<span class="token punctuation">.</span><span class="token function">query</span><span class="token punctuation">(</span><span class="token punctuation">)</span>                <span class="token punctuation">.</span><span class="token function">ne</span><span class="token punctuation">(</span><span class="token string">"status"</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">eq</span><span class="token punctuation">(</span><span class="token string">"id"</span><span class="token punctuation">,</span> key<span class="token punctuation">)</span>                <span class="token punctuation">.</span><span class="token function">one</span><span class="token punctuation">(</span><span class="token punctuation">)</span>        <span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@GetMapping</span><span class="token punctuation">(</span><span class="token string">"/stock/{id}"</span><span class="token punctuation">)</span>    <span class="token keyword">public</span> ItemStock <span class="token function">findStockById</span><span class="token punctuation">(</span><span class="token annotation punctuation">@PathVariable</span><span class="token punctuation">(</span><span class="token string">"id"</span><span class="token punctuation">)</span> Long id<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">//根据id查询库存的业务添加缓存，缓存未命中时查询数据库</span>        <span class="token keyword">return</span> stockCache<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>id<span class="token punctuation">,</span> key <span class="token operator">-</span><span class="token operator">></span> stockService<span class="token punctuation">.</span><span class="token function">getById</span><span class="token punctuation">(</span>key<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="③Lua语法入门"><a href="#③Lua语法入门" class="headerlink" title="③Lua语法入门"></a>③Lua语法入门</h2><h3 id="初识Lua"><a href="#初识Lua" class="headerlink" title="初识Lua"></a>初识Lua</h3><p>Lua 是一种轻量小巧的脚本语言，用标准C语言编写并以源代码形式开放， 其设计目的是为了嵌入应用程序中，从而为应用程序提供灵活的扩展和定制功能。官网：<a href="https://www.lua.org/">https://www.lua.org/</a></p><p>Lua经常嵌入到C语言开发的程序中，例如游戏开发、游戏插件等。Nginx本身也是C语言开发，因此也允许基于Lua做拓展。</p><h3 id="HelloWorld"><a href="#HelloWorld" class="headerlink" title="HelloWorld"></a>HelloWorld</h3><p>CentOS7默认已经安装了Lua语言环境，所以可以直接运行Lua代码。</p><p>1）在Linux虚拟机的任意目录下，新建一个hello.lua文件</p><p>2）添加下面的内容</p><pre class="line-numbers language-lua"><code class="language-lua"><span class="token function">print</span><span class="token punctuation">(</span><span class="token string">"Hello World!"</span><span class="token punctuation">)</span>  <span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><h3 id="变量和循环"><a href="#变量和循环" class="headerlink" title="变量和循环"></a>变量和循环</h3><h4 id="1-数据类型"><a href="#1-数据类型" class="headerlink" title="1.数据类型"></a>1.数据类型</h4><p>Lua中支持的常见数据类型包括：</p><table><thead><tr><th>数据类型</th><th>描述</th></tr></thead><tbody><tr><td>nil</td><td>表示一个无效值（在条件表达式中相当于false）。</td></tr><tr><td>boolean</td><td>包含两个值：false和true</td></tr><tr><td>number</td><td>表示双精度类型的实浮点数</td></tr><tr><td>string</td><td>字符串由一对双引号或单引号来表示</td></tr><tr><td>function</td><td>由 C  或 Lua  编写的函数</td></tr><tr><td>table</td><td>Lua 中的表（table）其实是一个”关联数组”（associative arrays），数组的索引可以是数字、字符串或表类型。在  Lua  里，table  的创建是通过”构造表达式”来完成，最简单构造表达式是{}，用来创建一个空表。</td></tr></tbody></table><p>另外，Lua提供了type()函数来判断一个变量的数据类型：</p><pre class="line-numbers language-lua"><code class="language-lua"><span class="token operator">></span> <span class="token function">print</span><span class="token punctuation">(</span><span class="token function">type</span><span class="token punctuation">(</span><span class="token string">"Hello world"</span><span class="token punctuation">)</span>）string<span class="token operator">></span> <span class="token function">print</span><span class="token punctuation">(</span><span class="token function">type</span><span class="token punctuation">(</span><span class="token number">10.4</span><span class="token operator">*</span><span class="token number">3</span><span class="token punctuation">)</span>）number<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><h4 id="2-声明变量"><a href="#2-声明变量" class="headerlink" title="2.声明变量"></a>2.声明变量</h4><p>Lua声明变量的时候无需指定数据类型，而是用local来声明变量为局部变量：</p><pre class="line-numbers language-lua"><code class="language-lua"><span class="token comment" spellcheck="true">-- 声明字符串，可以用单引号或双引号，</span><span class="token keyword">local</span> str <span class="token operator">=</span> <span class="token string">'hello'</span><span class="token comment" spellcheck="true">-- 字符串拼接可以使用 ..</span><span class="token keyword">local</span> str2 <span class="token operator">=</span> <span class="token string">'hello'</span> <span class="token operator">..</span> <span class="token string">'world'</span><span class="token comment" spellcheck="true">-- 声明数字</span><span class="token keyword">local</span> num <span class="token operator">=</span> <span class="token number">21</span><span class="token comment" spellcheck="true">-- 声明布尔类型</span><span class="token keyword">local</span> flag <span class="token operator">=</span> <span class="token keyword">true</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>Lua中的table既可以作为数组，又可以作为Java中的map来使用。数组就是特殊的table，key是数组角标而已：</p><pre class="line-numbers language-lua"><code class="language-lua"><span class="token comment" spellcheck="true">-- 声明数组 ，key为角标的 table</span><span class="token keyword">local</span> arr <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token string">'java'</span><span class="token punctuation">,</span> <span class="token string">'python'</span><span class="token punctuation">,</span> <span class="token string">'lua'</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">-- 声明table，类似java的map</span><span class="token keyword">local</span> map <span class="token operator">=</span>  <span class="token punctuation">{</span>name<span class="token operator">=</span><span class="token string">'Jack'</span><span class="token punctuation">,</span> age<span class="token operator">=</span><span class="token number">21</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>Lua中的数组角标是从1开始，访问的时候与Java中类似：</p><pre class="line-numbers language-lua"><code class="language-lua"><span class="token comment" spellcheck="true">-- 访问数组，lua数组的角标从1开始</span><span class="token function">print</span><span class="token punctuation">(</span>arr<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>Lua中的table可以用key来访问：</p><pre class="line-numbers language-lua"><code class="language-lua"><span class="token comment" spellcheck="true">-- 访问table</span><span class="token function">print</span><span class="token punctuation">(</span>map<span class="token punctuation">[</span><span class="token string">'name'</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token function">print</span><span class="token punctuation">(</span>map<span class="token punctuation">.</span>name<span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><h4 id="3-循环"><a href="#3-循环" class="headerlink" title="3.循环"></a>3.循环</h4><p>对于table，我们可以利用for循环来遍历。不过数组和普通table遍历略有差异。</p><p>遍历数组：</p><pre class="line-numbers language-lua"><code class="language-lua"><span class="token comment" spellcheck="true">-- 声明数组 key为索引的table</span><span class="token keyword">local</span> arr <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token string">'java'</span><span class="token punctuation">,</span> <span class="token string">'python'</span><span class="token punctuation">,</span> <span class="token string">'lua'</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">-- 遍历数组</span><span class="token keyword">for</span> index<span class="token punctuation">,</span>value <span class="token keyword">in</span> <span class="token function">ipairs</span><span class="token punctuation">(</span>arr<span class="token punctuation">)</span> <span class="token keyword">do</span>    <span class="token function">print</span><span class="token punctuation">(</span>index<span class="token punctuation">,</span> value<span class="token punctuation">)</span> <span class="token keyword">end</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>遍历普通table</p><pre class="line-numbers language-lua"><code class="language-lua"><span class="token comment" spellcheck="true">-- 声明map，也就是table</span><span class="token keyword">local</span> map <span class="token operator">=</span> <span class="token punctuation">{</span>name<span class="token operator">=</span><span class="token string">'Jack'</span><span class="token punctuation">,</span> age<span class="token operator">=</span><span class="token number">21</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">-- 遍历table</span><span class="token keyword">for</span> key<span class="token punctuation">,</span>value <span class="token keyword">in</span> <span class="token function">pairs</span><span class="token punctuation">(</span>map<span class="token punctuation">)</span> <span class="token keyword">do</span>   <span class="token function">print</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> value<span class="token punctuation">)</span> <span class="token keyword">end</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="条件控制、函数"><a href="#条件控制、函数" class="headerlink" title="条件控制、函数"></a>条件控制、函数</h3><h4 id="1-函数"><a href="#1-函数" class="headerlink" title="1.函数"></a>1.函数</h4><p>定义函数的语法：</p><pre class="line-numbers language-lua"><code class="language-lua"><span class="token keyword">function</span> 函数名<span class="token punctuation">(</span>argument1<span class="token punctuation">,</span> argument2<span class="token punctuation">...</span><span class="token punctuation">,</span> argumentn<span class="token punctuation">)</span>    <span class="token comment" spellcheck="true">-- 函数体</span>    <span class="token keyword">return</span> 返回值<span class="token keyword">end</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>例如，定义一个函数，用来打印数组：</p><pre class="line-numbers language-lua"><code class="language-lua"><span class="token keyword">function</span> <span class="token function">printArr</span><span class="token punctuation">(</span>arr<span class="token punctuation">)</span>    <span class="token keyword">for</span> index<span class="token punctuation">,</span> value <span class="token keyword">in</span> <span class="token function">ipairs</span><span class="token punctuation">(</span>arr<span class="token punctuation">)</span> <span class="token keyword">do</span>        <span class="token function">print</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span>    <span class="token keyword">end</span><span class="token keyword">end</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="2-条件控制"><a href="#2-条件控制" class="headerlink" title="2.条件控制"></a>2.条件控制</h4><p>类似Java的条件控制，例如if、else语法：</p><pre class="line-numbers language-lua"><code class="language-lua"><span class="token keyword">if</span><span class="token punctuation">(</span>布尔表达式<span class="token punctuation">)</span><span class="token keyword">then</span>   <span class="token comment" spellcheck="true">--[ 布尔表达式为 true 时执行该语句块 --]</span><span class="token keyword">else</span>   <span class="token comment" spellcheck="true">--[ 布尔表达式为 false 时执行该语句块 --]</span><span class="token keyword">end</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>与java不同，布尔表达式中的逻辑运算是基于英文单词：</p><table><thead><tr><th>操作符</th><th>描述</th><th>实例</th></tr></thead><tbody><tr><td>and</td><td>逻辑与操作符。  若 A 为  false，则返回  A，否则返回  B。</td><td>(A and B) 为  false。</td></tr><tr><td>or</td><td>逻辑或操作符。  若 A 为  true，则返回  A，否则返回  B。</td><td>(A or B) 为  true。</td></tr><tr><td>not</td><td>逻辑非操作符。与逻辑运算结果相反，如果条件为  true，逻辑非为  false。</td><td>not(A and B) 为  true。</td></tr></tbody></table><p>需求：自定义一个函数，可以打印table，当参数为nil时，打印错误信息</p><pre class="line-numbers language-lua"><code class="language-lua"><span class="token keyword">function</span> <span class="token function">printArr</span><span class="token punctuation">(</span>arr<span class="token punctuation">)</span>    <span class="token keyword">if</span> <span class="token keyword">not</span> arr <span class="token keyword">then</span>        <span class="token function">print</span><span class="token punctuation">(</span><span class="token string">'数组不能为空！'</span><span class="token punctuation">)</span>    <span class="token keyword">end</span>    <span class="token keyword">for</span> index<span class="token punctuation">,</span> value <span class="token keyword">in</span> <span class="token function">ipairs</span><span class="token punctuation">(</span>arr<span class="token punctuation">)</span> <span class="token keyword">do</span>        <span class="token function">print</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span>    <span class="token keyword">end</span><span class="token keyword">end</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="④实现多级缓存"><a href="#④实现多级缓存" class="headerlink" title="④实现多级缓存"></a>④实现多级缓存</h2><h2 id="⑤缓存同步"><a href="#⑤缓存同步" class="headerlink" title="⑤缓存同步"></a>⑤缓存同步</h2><h1 id="5-MQ高级"><a href="#5-MQ高级" class="headerlink" title="5.MQ高级"></a>5.MQ高级</h1><h1 id="Sponsor❤️"><a href="#Sponsor❤️" class="headerlink" title="Sponsor❤️"></a>Sponsor❤️</h1><p>您的支持是我不断前进的动力，如果您感觉本文对您有所帮助的话，可以考虑打赏一下本文，用以维持本博客的运营费用，拒绝白嫖，从你我做起！🥰🥰🥰</p><table>  <tbody>     <tr>         <td style="text-align:center;">支付宝</td>         <td style="text-align:center;">微信</td>     </tr>   <tr>    <td style="text-align:center;" ><img width="200" src="https://jwt1399.top/medias/reward/alipay.png"></td>          <td style="text-align:center;"><img width="200" src="https://jwt1399.top/medias/reward/sponor_wechat.png"></td>       </tr></tbody></table>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;高级篇包含微服务保护(流量控制，系统保护，熔断降级，服务授权)、分布式事务、多级缓存、Redis集群、可靠消息服务&lt;/p&gt;
&lt;/blockquote&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;img</summary>
        
      
    
    
    
    <category term="Spring" scheme="https://jwt1399.top/categories/Spring/"/>
    
    
    <category term="SpringCloud" scheme="https://jwt1399.top/tags/SpringCloud/"/>
    
  </entry>
  
  <entry>
    <title>可搜索加密：VFSA方案</title>
    <link href="https://jwt1399.top/posts/44711.html"/>
    <id>https://jwt1399.top/posts/44711.html</id>
    <published>2022-09-30T05:16:22.000Z</published>
    <updated>2024-08-03T02:14:59.068Z</updated>
    
    <content type="html"><![CDATA[<p><strong>论文名称：《Verifiable Fuzzy Multi-keyword Search over Encrypted Data with Adaptive Security》</strong></p><ul><li>作者：童秋云; 苗银斌; 熊健; 刘希萌; 金光·雷蒙德·周; 邓磊</li><li>单位：西安电子科技大学</li><li>期刊：<a href="https://ieeexplore.ieee.org/document/9714876">IEEE Transactions on Knowledge and Data Engineering</a>【CCF A】</li><li>时间：2022年02月</li></ul><table><thead><tr><th align="center">多关键字</th><th align="center">模糊搜索</th><th align="center">可验证</th><th align="center">动态更新</th><th align="center">排名</th></tr></thead><tbody><tr><td align="center">✅</td><td align="center">✅</td><td align="center">✅</td><td align="center">❌</td><td align="center">❌</td></tr><tr><td align="center"><strong>正确性</strong></td><td align="center"><strong>完整性</strong></td><td align="center"><strong>安全性</strong></td><td align="center"><strong>复杂度</strong></td><td align="center"></td></tr><tr><td align="center">✅</td><td align="center">✅</td><td align="center">IND-CKA2</td><td align="center">O(log m)</td><td align="center"></td></tr></tbody></table><h2 id="1、方案简介"><a href="#1、方案简介" class="headerlink" title="1、方案简介"></a>1、方案简介</h2><p>论文提出了一种具有<strong>自适应安全性的可验证模糊多关键字搜索</strong>（VFSA）方案。VFSA首先采用<code>局部敏感散列(LSH)</code>将拼写错误和正确的关键字<u>散列到相同的位置</u>，然后为每个文档设计一个<code>双布隆过滤器(TBF)</code>来<u>存储和屏蔽文档中包含的所有关键字</u>，然后<code>基于图的关键字分区算法(GKP)</code>构建索引树以实现自适应<code>亚线性检索</code>，最后将<code>Merkle哈希树(MHT)</code>结构与<code>自适应的多集累加器(MSA)</code>相结合，以<u>检查搜索结果的完整性和正确性</u>。通过安全分析表明，VFSA在<u>IND-CKA2</u>模式下是安全的，并实现了查询身份验证。使用真实数据集的实验证明了VFSA的实用性。</p><ul><li>首先，使用 <strong>LSH</strong> 和 <strong>TBF</strong> 来支持自适应安全的模糊多关键字搜索</li><li>然后，使用 <strong>MHT</strong> 和  <strong>GPK</strong> 构造索引树，获得比线性搜索复杂度更快的搜索效果</li><li>最后，将 <strong>MHT</strong> 与<strong>MSA</strong>相结合，实现结果的正确性和完整性验证</li></ul><p><img src="https://img.jwt1399.top/img/202209291228234.png"></p><h2 id="2、使用技术"><a href="#2、使用技术" class="headerlink" title="2、使用技术"></a>2、使用技术</h2><table><thead><tr><th>技术</th><th>作用</th></tr></thead><tbody><tr><td>局部敏感散列（LSH）</td><td>将拼写错误的关键字散列到相同的位置</td></tr><tr><td>双布隆过滤器（TBF）</td><td>存储和屏蔽文档中包含的所有关键字</td></tr><tr><td>Merkle哈希树（MHT）</td><td>正确性和完整性验证</td></tr><tr><td>自适应多集累加器（MSA）</td><td>正确性和完整性验证</td></tr><tr><td>基于图的关键字分区算法（GKP）</td><td>提高搜索效率</td></tr></tbody></table><h3 id="⓵Locality-Sensitive-Hashing"><a href="#⓵Locality-Sensitive-Hashing" class="headerlink" title="⓵Locality Sensitive Hashing"></a>⓵Locality Sensitive Hashing</h3><p>主要用于高效处理海量高维数据的最近邻问题 ，使得 2 个相似度很高的数据以较高的概率映射成同一个hash 值，而令 2 个相似度很低的数据以极低的概率映射成同一个 hash 值。</p><hr><h3 id="⓶Twin-Bloom-Filter"><a href="#⓶Twin-Bloom-Filter" class="headerlink" title="⓶Twin Bloom Filter"></a>⓶Twin Bloom Filter</h3><p>TBF 是 BF 的一种扩展的数据结构。具体来说，TBF 是一个有 N 个双胞胎的位数组，其中每个双胞胎包含两个存储相反位的单元。TBF 可以掩盖插入位置和插入元素的数量，即可以同时存储和屏蔽关键字信息，而 BF 则完全暴露它们。TBF 包含四种算法：</p><p><img src="https://img.jwt1399.top/img/202209272227152.png"></p><blockquote><p>❶$Setup(1^λ, k)→ {H_{key}, H}$</p></blockquote><p>k+1 键控哈希函数 $H_{key}$ 可以使用 HMAC 生成 ，$ h_i(·) &#x3D;HMAC (K_i, ·) $ ， $K_i$ 是的随机生成的密钥，长度为 λ 。哈希函数 H 可以使用随机预言机 $\mathbb{H}:{0,1}^∗→{0,1}^∗$ 生成，$H &#x3D;\mathbb{H}(·)%2$</p><p>$$<br>H_{key} &#x3D; {h_i：{0,1}^∗ × {0,1}^λ → {0,1}^∗}<br>\\<br> H：{0,1}^∗→[0,1]<br>$$</p><blockquote><p>❷$Initi(N, H_{key}, H, γ)→B$</p></blockquote><p>生成长度为 N 的 TBF，对于每个双胞胎，选择的单元格和未选择的单元格分别初始化为 0 和 1<br>$$<br>B[ι][H(h_{k+1}(ι)⊕γ)] &#x3D; 0<br>\\<br>B[ι][1−H(h_{k+1}(ι)⊕γ)] &#x3D; 1, ι ∈ [1,N]<br>$$<br>γ 是一个随机数，用于消除不同 TBF 之间的相关性。否则，$B_i$ 和 $B_j$ 中第 $ι$ 个双胞胎选择的单元格是相同的，这降低了随机性，增加了数据暴露的风险</p><blockquote><p>❸$Insert(B,  w, H_{key}, H, γ) → B$</p></blockquote><p>对集合 $W$ 中的每个元素 $w$ ，将其散列到 k 个位置 $B[h_1(w)]，····，B[h_k(w)]$，然后对这 k 个位置选中的单元格设置为1，未选中的单元格设置为 0<br>$$<br>h_i(w) &#x3D; h_i(w)\ mod \ N,i ∈ [1,k]<br>\\<br>B[h_i(w)][H(h_{k+1}(h_i(w))⊕γ)] &#x3D; 1<br>\\<br>B[h_i(w)][1−H(h_{k+1}(h_i(w))⊕γ)] &#x3D; 0, i∈[1, k]<br>$$</p><blockquote><p>❹$Check(B, w’, γ)→0&#x2F;1$</p></blockquote><p>为了测试一个元素 $w’$ 是否存储在 $B$ 中，计算并检查下式。如果它对每个 $i ∈ [1,k]$成立，那么 $w’$ 在 B 中</p><p>$$<br>B[h_i(w’)][H(h_{k+1}(h_i(w’))⊕γ)]\overset{\text{?}}{&#x3D;}1<br>$$</p><hr><h3 id="⓷Merkle-hash-tree"><a href="#⓷Merkle-hash-tree" class="headerlink" title="⓷Merkle hash tree"></a>⓷Merkle hash tree</h3><p>MHT 是一个简单而有效的身份验证结构模型，以二叉哈希树的形式呈现。MHT 的叶子节点是真实数据值的哈希值，非叶子节点是左右子节点哈希值的串联。<br>$$<br>h(h(left \ child)||h(right \ child))<br>$$</p><p>MHT 就是用来做完整性校验的， 允许对大规模数据的内容进行高效、安全的验证。但是，MHT有一个节点平衡的缺点，在插入或删除一系列请求后发生。因此，此技术不直接适用于可证明的数据拥有方案。</p><p>但是将 MHT 数据结构（Merkle，1980）和双线性聚合签名（Boneh等人，2003）相结合，可以解决 MHT 中的节点平衡问题。当数据所有者决定更新数据块时，她会将新数据块及其身份验证标记发送到云。收到更新请求后，云执行更新操作，重新生成 MHT 的根，更新聚合块标签并返回已签名的根 $sign_{pr} (root)$</p><p><img src="https://img.jwt1399.top/img/202210301636219.png"></p><p>参考：<a href="https://www.sciencedirect.com/topics/computer-science/merkle-hash-tree">Merkle Hash Tree | ScienceDirect Topics</a>、<a href="https://blog.csdn.net/freedomhero/article/details/124943936?spm=1001.2014.3001.5502">双线性配对</a>、<a href="https://blog.csdn.net/freedomhero/article/details/125203129">双线性配对- BLS 签名</a></p><hr><h3 id="⓸adapted-multiset-accumulator"><a href="#⓸adapted-multiset-accumulator" class="headerlink" title="⓸adapted multiset accumulator"></a>⓸adapted multiset accumulator</h3><p>自适应多集累加器（MSA）是通过从 CSP 中集合 $T_i$ 得到的，是一种基于双线性对 $(p,\mathbb{G},\mathbb{G}_T, e, g)$ 为两个集合 $T_1$ , $T_2$ 的不相交提供简短证明的有效方法。其中$\mathbb{G}$,$\mathbb{G}_T$ 是素数阶 $p$ 的两个乘法循环群，$g$ 是 $\mathbb{G}$ 的生成器，$e：\mathbb{G}×\mathbb{G}→\mathbb{G}_T$ 是一个双线性映射。MSA由以下四种算法组成：©</p><blockquote><p>❶$KGen(1^λ)→{sk, pk}$</p></blockquote><p>随机选择一个元素 $u∈\mathbb{Z}^*_p$ 作为私钥 $sk$，生成 $g^u$ 作为公钥 $pk$</p><blockquote><p>❷$Acc(T_i, sk)→acc_i$</p></blockquote><p>给定集合 $T_i$，$T_i$ 的累积值可计算为 $acc_i&#x3D;g^{P(T_i)}&#x3D; g^{r_i∏_{t∈T_i}(t+sk)}$，其中 $r_i$ 是 $\Z^*_p$ 中的一个随机元素</p><blockquote><p>❸$PDisjoint(P(T_1), P(T_2), pk)→π$</p></blockquote><p>如果 $T_1∩T_2&#x3D;\varnothing$，则可计算不匹配证明 $π&#x3D; (pk^{Q_1}, pk^{Q_2})$，根据扩展欧氏算法， $Q_1$、$Q_2$ 是满足 $P(T_1)Q_1+P(T_2)Q_2&#x3D; 1$ 的两个多项式</p><blockquote><p>❹$VDisjoint(acc_1, acc_2, π, sk)→{0,1}$</p></blockquote><p>$T_1∩T_2&#x3D;\varnothing$ 当且仅当 $e(acc_1, pk^{Q_1})·e(acc_2, pk^{Q_2}) &#x3D;e(g,g)^{sk}$ 成立</p><hr><p>请注意，CSP无法通过密文$P(T_1)$获得 $T_i$ 和 $sk$。因此，它不能通过伪造累积值来伪造不匹配证明</p><h3 id="⓹Graph-based-keyword-partition"><a href="#⓹Graph-based-keyword-partition" class="headerlink" title="⓹Graph-based keyword partition"></a>⓹Graph-based keyword partition</h3><blockquote><p>索引树中的文档分布对查询效率有很大影响，因为与左右子树都匹配的子查询将导致遍历两个子树。因此，使用基于图的关键字划分算法(GKP)来尽可能减小遍历宽度，其主要思想是最小化左右索引子树中相同关键字的数量</p></blockquote><p>将每个文档 $f_i$ 作为顶点 $v_i$，两个文档 $f_i, f_j$ 相同关键词的个数为 $v_i$ 和 $v_j$ 之间的边的权值 $ω_{i,j}$，DO 构造了一个加权无向图 G 。然后，DO 按照下图的步骤将 $G$ 中的 $m$ 个顶点合并为两个顶点。为了保证索引是一个平衡二叉树，只能合并两个顶点 $v_i$ , $v_j$ 满足 $|v_i|+|v_j|≤⌈m&#x2F;2⌉$，其中 $|v_i|$ 为顶点 $v_i$ 中包含的文档数量。</p><p><img src="https://img.jwt1399.top/img/202209292031824.png"></p><p>在所有可合并的顶点对中，DO首先选择边权值最大的对 ($v_i$ , $v_j$)，然后将它们合并成一个新的顶点 $v_ρ$，最后更新与之相连的每条边的权值，边权满足 $ω_{ρ，s}≤ω_{i,s}+ω_{s,j}$ 。合并过程不断重复，直到没有两个顶点可以合并为止。在那之后，会发生两种情况</p><ul><li>Case 1：</li></ul><p>结果图中只剩下两个顶点。DO 将这两个顶点作为的 $root$ 的左右子树，并将基于图的关键字分区算法分别应用于这两个点中包含的文档子集</p><ul><li>Case 2：</li></ul><p>结果图中还剩下三个顶点 $v_{τ_1}, v_{τ_2}, v_{τ_3}$ 。DO 首先找出包含最少文档数量的顶点，然后将顶点中的所有文档重新分配到其他两个顶点。具体而言，我们假设 $|v_{τ_1}|≥|v_{τ_2}|≥|v_{τ_3}|$。对于每个文档 $f_i∈v_{τ_3}$, DO 计算它与 $v_{τ_1}、v_{τ_2}$相同关键字的个数 (表示权重 $ω_{i,τ_1}， ω_{i,τ_2}$) 。在所有这些权重中，DO 选择了最高的一个。假设最大权重 $ω_{i,τ_1}$, DO 比较 $|v_{τ_1}|$ 与$⌈m&#x2F;2⌉$。如果 $|v_{τ_1}|≤⌈m&#x2F;2⌉$，DO 将 $f_i$ 合并到$v_{τ_1}$中，重新计算 $v_{τ_3}$ 和 $v_{τ_1}$、$v_{τ_2}$ 中各文档之间的新权重，并重复这个重分配过程。否则，DO会将 $v_{τ_3}$ 中的所有文档合并到 $v_{τ_2}$ 中。</p><p>例如，在上图中，我们注意到 $v_7, v_8, v_9$ 中的任何两个顶点都是不可合并的，因为$|v_7|&#x3D;|v_8|&#x3D;|v_9|&#x3D; 2$。在不丧失一般性的情况下，我们取 $v_7$ 为包含文档数量最少的顶点，并假设 $v_7$ 包含文档 $f_1, f_2$。</p><p>在 $f_i(i&#x3D; 1,2)$ 和 $v_8, v_9$ 之间的权重中，我们发现最高的权重是 $ω_{2,9}&#x3D; 3$。由于$|v_9|&#x3D; 2&lt;⌈5&#x2F;2⌉$保持不变，我们将$f_2$ 合并到 $v_9$ 以生成新的顶点 $v_{10}$。之后，在顶点 $v_1、v_8、v_{10}$ 中分别包含1、2、3个文档。虽然最高权值是$ω_{1,10}&#x3D; 5$，但我们将 $f_1$ 合并到 $v_8$ 中以生成新的顶点 $v_{11}$，因为$|v_{10}|&#x3D;⌈5&#x2F;2⌉$。</p><hr><p>举个🌰：下图是基于GKP的五个文档 ${f_1，f_2，f_3，f_4，f_5}$ 的索引树构造示例</p><p><img src="https://img.jwt1399.top/img/202209292030636.png"></p><p><strong>步骤一：</strong>顶点对 $(v_4, v_5)，(v_2, v_3)，(v_2, v_4)$ 具有最高的边权 $ω_{4,5}&#x3D;ω_{2,3}&#x3D;ω_{1,3}&#x3D; 2$，在不丧失一般性的情况下，我们合并 $v_4, v_5$ 以获得一个新的顶点 $v_6$，</p><p><strong>步骤二：</strong>为简便起见，假设 $ω_{ρ,s}&#x3D;ω_{i,s}+ω_{s,j}$，重新计算与 $v_6$ 相连的每条边的权值<br>$$<br>ω_{1,6}&#x3D;ω_{1,4} + ω_{1,5} &#x3D; 0+1&#x3D; 1<br>\<br>ω_{2,6} &#x3D;ω_{2,4} +ω_{2,5} &#x3D;0+1&#x3D; 1<br>\<br>ω_{3,6} &#x3D;ω_{3,4} +ω_{3,5} &#x3D;0+1&#x3D; 1<br>$$<br>在新的图中，最大边权值是 $ω_{2,3}&#x3D;ω_{1,3}&#x3D; 2$，在不丧失一般性的情况下，我们合并 $v_2, v_3$ 得到一个新的顶点 $v_7$</p><p><strong>步骤三：</strong>当前图由 $v_1、v_6、v_7$ 三个顶点组成，分别包含1、2、2个文档。重新计算权重<br>$$<br>|v_7|≥|v_6|≥|v_1|<br>\<br>v_1&#x3D;{f_1}&#x3D;{w_1,w_3}\<br>v_6&#x3D;{f_4,f_5}&#x3D;{w_1,w_2,w_4}\<br>v_7&#x3D;{f_2,f_3}&#x3D;{w_1,w_3,w_5}<br>\<br>ω_{1,7}&#x3D; 2， ω_{6,7}&#x3D; 1<br>$$<br>由于最高边权值是 $ω_{1,7}&#x3D; 2$ 和 $|v_1|+|v_7|&#x3D; 3≤⌈5&#x2F;2⌉$，我们合并 $v_1, v_7$ 得到一个新的顶点 $v_8$</p><p><strong>步骤四：</strong>最后，我们将 $v_6、v_8$ 作为 $root$ 的左右子树来构建索引树。为了生成基于 GKP 的认证索引树，我们只需要将 $Algorithms \ 2$ 中的第8-9行替换为 $ID_l,ID_r←GKP(ID)$</p><h2 id="3、流程概述"><a href="#3、流程概述" class="headerlink" title="3、流程概述"></a>3、流程概述</h2><blockquote><p>方案设计了一种自适应安全的模糊多关键字搜索机制。对于每个文档，我们首先使用LSH函数将拼写错误的关键字散列到相同的位置，然后设计一个TBF来存储和屏蔽这些散列值，以在IND-CKA2模型下实现隐私保护的模糊多关键字搜索。在查询阶段实现了比线性搜索复杂度更快的搜索。我们将所有文档的TBF作为叶节点来构建平衡索引树。在构建索引树时，我们使用基于图的关键字划分算法来最小化遍历宽度，从而进一步提高搜索效率。方案实现了正确性和完整性验证。我们首先使用自适应的多集累加器对未返回文档的不相交性和相应的查询关键字生成不匹配证明，以确保不丢失有效结果，然后通过MHT对索引树进行身份验证，以确保索引树中存储的内容不被篡改。</p></blockquote><ul><li><p>⓵首先为每个文档建立索引，然后基于这些索引构建索引树，最后检索包含所有查询关键字的文档。</p></li><li><p>⓶对于自适应安全的模糊多关键字搜索，每个文档的索引是一个 TBF，包含文档中的所有关键字。</p><ul><li>具体来说，方案首先使用 LSH 处理每个关键字，这样拼写错误和正确的关键字就可以散列到相同的 bucket 中。</li><li>然后，方案将这些关键字的 bucket 值存储并屏蔽到 TBF 中，以实现自适应安全。这样，就生成了每个文档的索引。</li></ul></li><li><p>⓷对于比线性搜索复杂度更快的搜索，方案通过将所有文档的索引作为叶节点来构建一个平衡的二叉树作为索引树。</p><ul><li>索引树中的每个非叶节点也是一个TBF，由其子节点表示的所有关键字的联合构成。</li><li>如果非叶节点不包含查询关键字，则其子节点肯定不满足查询条件，因此可以修剪以非叶节点为根的子树中存储的文档，以实现次线性检索。</li><li>此外，在索引树的构建中，考虑文档分布对检索效率的影响，采用基于图的关键字划分算法来最小化左右索引子树中相同关键字的数量，以进一步提高检索效率。</li></ul></li><li><p>⓸为了进行正确性和完整性验证，首先使用 MSA 对未返回文档的不相交性和相应的查询关键字生成不匹配证明，以确保不丢失有效结果；然后通过MHT对索引树进行身份验证，以确保索引树中存储的内容不被篡改。</p><ul><li><p>首先方案运行 $MSA.Acc$ 分别计算每个树节点和每个查询关键字的累积值</p></li><li><p>如果树节点不包含某个查询关键字，则方案运行 $MSA.PDisjoint$ 生成一个不匹配证明表示它们的不相交。</p></li><li><p>在不匹配证明的帮助下，QU 运行$MSA.VDisjoint$，验证未返回的文档是否确实不满足查询条件，以确保没有遗漏有效结果。</p></li><li><p>使用 MSA 来验证搜索结果的完整性的前提是索引树被完整地存储，没有被篡改，这是由 MHT 来保证的。</p></li><li><p>方案根据 MHT 对存储在每个树节点中的内容进行散列。 root digest 采用数字签名技术进行签名，以防止被篡改。</p></li><li><p>如果原始 root digest 等于基于辅助证明信息重新计算的 root digest，则索引树没有被篡改。这样，实现了对搜索结果的正确性和完整性的验证。</p></li></ul></li></ul><h2 id="4、具体实现"><a href="#4、具体实现" class="headerlink" title="4、具体实现"></a>4、具体实现</h2><h3 id="❶KeyGen"><a href="#❶KeyGen" class="headerlink" title="❶KeyGen"></a><strong>❶KeyGen</strong></h3><blockquote><p>$KeyGen(1^λ, k)→ {PP, SK}$</p></blockquote><ul><li><p>⓵DO首先运行 $MSA.KGen→{pk, sk}$，用于结果验证</p></li><li><p>⓶再生成一对公私对 ${K_{pub}, K_{pri}}$ ，用于数字签名</p></li><li><p>⓷再运行  $TBF.Setup$ 输出 $H_{key}$ 和 $H$</p></li><li><p>⓸再生成一个键控哈希函数 $H_1 : {0,1}^∗× {0,1}^λ→ \mathbb{Z}_p$</p></li><li><p>⓹再随机生成传统的抗碰撞哈希 $H_2 : {0,1}^∗→ {0,1}^λ$</p></li></ul><p>DO 最终输出 $PP&#x3D;{pk, K_{pub}, H, H_2},$ $SK&#x3D;{sk,K_{pri},H_{key}, H_1}$</p><h3 id="❷IndexGen"><a href="#❷IndexGen" class="headerlink" title="❷IndexGen"></a><strong>❷IndexGen</strong></h3><blockquote><p>$IndexGen(F, N, PP, SK)→I$</p></blockquote><p>从文档 $F &#x3D; {f_1,···, f_m}$ 中采用 <em>Porter Stemming Algorithm</em> 提取关键词 $W&#x3D;{w_1, w_2,···}$；DO 通过 $Algorithms \ 1,2$ 生成经过身份验证的索引树</p><table><thead><tr><th><img src="https://img.jwt1399.top/img/202209281433785.png"></th><th><img src="https://img.jwt1399.top/img/202209281722510.png"></th></tr></thead></table><table><thead><tr><th>Fuzzy setting  &amp; adaptive encryption</th><th>Index tree  &amp;  Authentication</th></tr></thead><tbody><tr><td><img src="https://img.jwt1399.top/img/202209261409397.png"></td><td><img src="https://img.jwt1399.top/img/202209261409241.png"></td></tr></tbody></table><h4 id="⓵Fuzzy-setting"><a href="#⓵Fuzzy-setting" class="headerlink" title="⓵Fuzzy setting"></a>⓵Fuzzy setting</h4><ul><li>160 uni-gram vector $\vec{v_j}$</li><li>LSH family： $ {h_{a_ι,b_ι}}^n_{ι&#x3D;1}$</li></ul><p>$$<br>S_j&#x3D;h_{a_1,b_1}(\vec{v_j})||···||h_{a_n,b_n}(\vec{v_j})<br>$$</p><hr><p>举个🌰：将单词 “secure” 放入 uni-gram 集合</p><ul><li><p>1.首先分片： ${s1，e1，c1，u1，r1，e2}$</p></li><li><p>2.计算位置：${19,5,3,21,18,26+5}$ </p></li><li><p>3.设置值：对应位置设为1，其他位置设置为0，生成160维 uni-gram 集合</p></li><li><p>4.映射到Bucket：使用 3 个 LSH ${h_{a_1,b_1}，h_{a_2,b_2}，h_{a_3,b_3}}$，将 $\vec{v_j}$ 散列为 3 个哈希值，如 $s_1，s_2，s_3$，并生成 Bucket String $S&#x3D;s_1||s_2||s_3$</p></li><li><p>假设文档包含三个关键字，我们将相应的 3 个 Bucket String 映射到 TBF $B_i$</p></li></ul><p><font color="#f0f"><b>温馨提示</b></font>：uni-gram 向量的长度和 LSH 函数的数量可以根据 DO 的需要改变。</p><hr><h4 id="⓶Adaptive-encryption"><a href="#⓶Adaptive-encryption" class="headerlink" title="⓶Adaptive encryption"></a>⓶Adaptive encryption</h4><ul><li><p>DO 运行 $TBF.Initi$ 生成长度为 N 的 TBF $B_i$</p></li><li><p>再运行 $TBF.Insert$ 将 Bucket String  $S_j$ 映射到 $B_i$<br>$$<br>B_i[h_1(S_j)],···, B_i[h_k(S_j)]<br>$$</p></li><li><p>再生成 $tag$  $t_j&#x3D;H_1(h_1(S_j)||···||h_k(S_j))$</p></li><li><p>$T_i $ 由 $f_i$ 所包含的关键字的所有标签组成，用于稍后生成累积值</p></li></ul><hr><h4 id="⓷Index-tree-construction"><a href="#⓷Index-tree-construction" class="headerlink" title="⓷Index tree construction"></a>⓷Index tree construction</h4><p>为了实现亚线性检索，DO 将所有文档的 TBF 作为叶节点来构造平衡的二进制索引树，索引树中的每个非叶节点也是一个 n 维 TBF，由其子节点表示的所有关键字集的并集构造而成。</p><ul><li>设 $B_l$ 和 $B_r$ 是存储在节点 $L_f$ 的左右子节点中的两个 TBF ，存储在节点 $L_f$ 中的 TBF $B_f$ 构造如下：</li></ul><p>$$<br>B_f &#x3D; B_l ∨ B_r<br>\\<br>B_f[ι][H(h_{k+1}(ι)⊕γ_f)] &#x3D;B_l[ι][H(h_{k+1}(ι)⊕γ_l)]∨B_r[ι][H(h_{k+1}(ι)⊕γ_r)]<br>\\<br>B_f[ι][1−H(h_{k+1}(ι)⊕γ_f)] &#x3D; 1−B_f[ι][H(h_{k+1}(ι)⊕γ_f)]<br>$$</p><hr><h4 id="⓸Authentication"><a href="#⓸Authentication" class="headerlink" title="⓸Authentication"></a>⓸Authentication</h4><p>DO使用 MSA 和 MHT 验证索引树，如下所示</p><ul><li>1.对于表示每个文档 $f_i$ 的叶节点，DO 生成一个累积值 $acc_i$ 和一个摘要 $d_i$</li></ul><p>$$<br>acc_i&#x3D;MSA.Acc(T_i, sk)<br>\\<br>d_i&#x3D;H_2(c_i||B_i||acc_i)<br>$$</p><p><del>其中密文$c_i$是通过标准加密算法（如AES）加密文档而生成的</del></p><ul><li>2.对于每个非叶节点 $L_f$，DO 生成一个累积值  $acc_f$ 和一个摘要 $d_f$</li></ul><p>$$<br>acc_f&#x3D;MSA.Acc(T_f, sk)<br>\\<br>d_f&#x3D;H_2(H_2(d_l||d_r)||B_f||acc_f)<br>$$</p><p><del>其中 $T_f$ 是其子节点 $tag$ 集的并集($T_f&#x3D;T_l∪T_r$)。$d_l$，$d_r$ 是其左右子节点的摘要</del></p><ul><li>3.计算 root 摘要后，DO对其进行签名以获得 $Sig&#x3D;sign_{K_{pri}}(d_{root})$</li></ul><hr><p>举个🌰：给定 5 个文档  ${f_1, f_2, f_3, f_4, f_5}$, 其中 $f_1&#x3D;{w_1, w_3}$, $f_2&#x3D;{w_1, w_5}$, $f_3&#x3D;{w_1, w_3, w_5}$, $ f_4&#x3D;{w_2, w_4}$, $f_5&#x3D;{w_1, w_2, w_4}$</p><ul><li><p>1.首先将它们转换为 5 个 TBF，然后以 5 个TBF为叶节点构造平衡二叉索引树，实现亚线性检索。在使用 MSA 和 MHT 对其进行身份验证后</p></li><li><p>每个非叶节点存储五个组件：$&lt;B，P(T)，γ，acc，d&gt;$</p></li><li><p>每个叶节点多存储一个组件：密文 c</p></li><li><p>根节点多存储一个组件：签名 Sig</p></li><li><p>2.假设关键字 $w_1$ 的 Bucket String 散列到位置 1、2、3，其标签 $t_1&#x3D;H_1(1||2||3)$类似地，$w_2、w_4、w_5$ 标签是计算 $t_2、t_4、t_5$</p></li><li><p>3.将叶节点 $L_2$ 的累积值和摘要计算为 $acc_2&#x3D;MSA.Acc({t_1，t_5}，sk)$ 和 $d_2&#x3D;H_2(c_2||B_2||acc_2)$</p></li><li><p>4.非叶节点 $L_6$ 的累积值和摘要计算为 $acc_6&#x3D;MSA.Acc({t_1，t_2，t_4}，sk)$ 和 $d_6&#x3D;H_2(H_2(d_4||d_5)||B_6||acc_6)$</p></li></ul><hr><h3 id="❸TrapGen"><a href="#❸TrapGen" class="headerlink" title="❸TrapGen"></a>❸TrapGen</h3><blockquote><p>$TrapGen(Q, SK)→ {TK, T_Q}$</p></blockquote><p>对于每个查询关键字 $w_i’∈Q$，QU 首先像 DO 在模糊设置步骤中一样将其转换为Bucket String $S_i’$，然后将 $S_i’$ 加密为 $tk_i$，并生成标签 $t_i’$ </p><p>$$<br>tk_i&#x3D;{(h_1(S_i’), h_{k+1}(h_1(S_i’))),···,(h_k(S_i’), h_{k+1}(h_k(S_i’)))},<br>\\<br>t_i’&#x3D;H_1(h_1(S_i’)||···||h_k(S_i’))<br>$$<br>这样，QU 得到陷门 $TK &#x3D; {tk_1，···，tk_q}$ 和标签集 $T_Q&#x3D;{t_1’，···，t_q’ }$，其中$q$ 为搜索查询 $Q$ 中包含的查询关键词的个数</p><h3 id="❹Search"><a href="#❹Search" class="headerlink" title="❹Search"></a>❹Search</h3><blockquote><p>$Search(I, TK, T_Q, PP)→ {R, AP}$</p></blockquote><p><img src="https://img.jwt1399.top/img/202209282044990.png"></p><p>收到搜索请求 $TK &#x3D; {tk_1，···，tk_q}$ 后，CSP 从根节点检索经过身份验证的索引树 $I$，通过 $Algorithm \ 3$ 查找包含所有查询关键字的叶节点</p><ul><li><p>从根节点开始，CSP 检查当前节点 node 是否包含所有查询关键字(第1-8行)</p></li><li><p>如果 $node.B[h_ι(S_i’)][H(h_{k+1}(h_ι(S_i’))⊕node.γ)] &#x3D; 1$ 适用于 $∀ι ∈ [1,k]， i∈[1,q]$，则返回1，表示该节点包含所有查询关键字。</p></li><li><p>然后，CSP 检查当前节点 node 是否是叶子节点。如果是，CSP将相应的密文 $c_i$ 添加到搜索结果集 $R$ (第10-11行)</p></li><li><p>之后，CSP 添加元组 $⟨node.B, node.acc⟩$ 到辅助证明集 $AP$，并以相同的方式处理其左右子树(第12-14行)</p></li><li><p>如果 $∃i∈[1, q], ι∈[1, k], node.B[h_ι(S_i’)][H(h_{k+1}(h_ι(S_i’))⊕node.γ)] &#x3D; 0$，它返回标签  $t_i’$ ，表示该 node 不包含查询关键字 $w_i’$。</p></li><li><p>然后, CSP 运行$MSA.PDisjoint$ 为当前节点 node 和查询关键字 $w_i’$ 生成不匹配证明 $\pi$，并添加元组 $⟨node.d, node.acc, t_i’, π⟩$ 到 $AP$ (第16-17行)</p></li></ul><hr><p>🙋🏻‍♂️🌰：当查询为 $Q&#x3D;{w_1，w_3}$ 时辅助证明集的示例</p><ul><li><p>在 5 个文档中，只有文档 $f_1、f_3$ 包含这两个关键字。</p></li><li><p>因此，返回 $c_1、c_3$ 作为搜索结果</p></li><li><p>辅助证明集 $AP$ 由搜索路径上各节点的 TBF $B_i$ 和累积值 $acc$、搜索路径外各节点的摘要 $d_i$ 、累积值 $acc$、不匹配标记 $t_i’$ 和不匹配证明 $\pi$ 以及签名 Sig 组成。</p></li></ul><p><img src="https://img.jwt1399.top/img/202209282044090.png"></p><h3 id="❺Verify"><a href="#❺Verify" class="headerlink" title="❺Verify"></a>❺Verify</h3><blockquote><p>$Verify(R, AP, PP, SK)→1&#x2F;0$</p></blockquote><p>使用辅助证明集 $AP$，验证者首先运行 $MSA.VDisjoint$ 检查搜索查询 $Q$ 是否确实与未返回的文档不匹配，然后重构最小认证树，并将其 $root$ 摘要与原始 $root$ 摘要进行比较，以验证搜索结果的正确性和完整性。</p><p>在我们的例子中，验证者首先检查公式 14，以证明文档 $f_2, f_4, f_5$ 不包含查询关键字 $w_3$，这确实与搜索查询 $Q&#x3D;{w_1, w_3}$ 不匹配。</p><p>然后验证者使用对应的公钥对签名 $Sig$ 进行解密，得到原始根摘要 $d_{root}$，即 $d_{root}&#x3D;ver_{K_{pub}}(Sig)$。最后，验证者重新计算公式15中的根摘要 $d_{root}’$，并将其与根进行比较。如果它们相等，则搜索结果是正确和完整的</p><p>$$<br>MSA.VDisjoint(acc_2,MSA.Acc(t_3, sk), π_2, sk) &#x3D; 1<br>\\<br>MSA.VDisjoint(acc_6,MSA.Acc(t_3, sk), π_6, sk) &#x3D; 1<br>$$</p><p>$$<br>d_i’&#x3D;H_2(c_i||B_i||acc_i), i&#x3D; 1,3<br>\\<br>d_7’&#x3D;H_2(H_2(d_3’||d_2)||B_7||acc_7)<br>\\<br>d_8’&#x3D;H_2(H_2(d_7’||d_1’)||B_8||acc_8)<br>\\<br>d_{root}’&#x3D;H_2(H_2(d_8’||d_6)||B_{root}||acc_{root})<br>$$</p><h2 id="Sponsor❤️"><a href="#Sponsor❤️" class="headerlink" title="Sponsor❤️"></a>Sponsor❤️</h2><p>您的支持是我不断前进的动力，如果您感觉本文对您有所帮助的话，可以考虑打赏一下本文，用以维持本博客的运营费用，拒绝白嫖，从你我做起！🥰🥰🥰</p><table>  <tbody>     <tr>         <td style="text-align:center;">支付宝</td>         <td style="text-align:center;">微信</td>     </tr>   <tr>    <td style="text-align:center;" ><img width="200" src="https://jwt1399.top/medias/reward/alipay.png"></td>          <td style="text-align:center;"><img width="200" src="https://jwt1399.top/medias/reward/sponor_wechat.png"></td>       </tr></tbody></table>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;p&gt;&lt;strong&gt;论文名称：《Verifiable Fuzzy Multi-keyword Search over Encrypted Data with Adaptive Security》&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;作者：童秋云; 苗银斌; 熊健;</summary>
        
      
    
    
    
    <category term="Data" scheme="https://jwt1399.top/categories/Data/"/>
    
    
    <category term="可搜索加密" scheme="https://jwt1399.top/tags/%E5%8F%AF%E6%90%9C%E7%B4%A2%E5%8A%A0%E5%AF%86/"/>
    
  </entry>
  
  <entry>
    <title>SpringCloud-实用篇</title>
    <link href="https://jwt1399.top/posts/42622.html"/>
    <id>https://jwt1399.top/posts/42622.html</id>
    <published>2022-09-20T04:07:04.000Z</published>
    <updated>2022-10-10T14:44:21.995Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>实用篇包含微服务治理(注册发现，远程调用，配置管理，网关路由)、Docker技术、异步通信、分布式缓存、分布式搜索</p></blockquote><p>小简从 0 开始学 Java 知识之 <a href="https://jwt1399.top/posts/29829.html">Java-学习路线</a> 中的《SpringCloud-实用篇》，不定期更新所学笔记，期待一年后的蜕变吧！&lt;有同样想法的小伙伴，可以联系我一起交流学习哦！&gt;</p><ul><li><input checked="" disabled="" type="checkbox"> 🚩时间安排：预计10天更新完</li><li><input checked="" disabled="" type="checkbox"> 🎯开始时间：09-20</li><li><input checked="" disabled="" type="checkbox"> 🎉结束时间：09-30</li><li><input checked="" disabled="" type="checkbox"> 🍀总结：</li></ul><table><thead><tr><th><img src="https://img.jwt1399.top/img/202209201412578.png" alt="学习安排"></th></tr></thead><tbody><tr><td><img src="https://img.jwt1399.top/img/202209201436751.png" alt="技术分类"></td></tr></tbody></table><h1 id="1-微服务"><a href="#1-微服务" class="headerlink" title="1.微服务"></a>1.微服务</h1><h2 id="①架构对比"><a href="#①架构对比" class="headerlink" title="①架构对比"></a>①架构对比</h2><table><thead><tr><th><code>架构</code></th><th><code>单体架构</code></th><th><code>分布式架构</code></th></tr></thead><tbody><tr><td><strong><code>描述</code></strong></td><td>将业务的所有功能集中在一个项目中开发，打成一个包部署。</td><td>根据业务功能对系统做拆分，每个业务功能模块作为独立项目开发。</td></tr><tr><td><strong><code>图示</code></strong></td><td><img src="https://img.jwt1399.top/img/202209201338897.png"></td><td><img src="https://img.jwt1399.top/img/202209201338887.png"></td></tr><tr><td><strong><code>优点</code></strong></td><td>架构简单、部署成本低</td><td>降低服务耦合、有利于服务升级和拓展</td></tr><tr><td><strong><code>缺点</code></strong></td><td>耦合度高（维护困难、升级困难）</td><td>服务调用关系错综复杂</td></tr></tbody></table><p>分布式架构虽然降低了服务耦合，但是服务拆分时也有很多问题需要思考：</p><ul><li>服务拆分的粒度如何界定？</li><li>服务集群地址如何维护？</li><li>服务的调用关系如何管理？</li><li>服务健康状态如何感知？</li></ul><p>人们需要制定一套行之有效的标准来约束分布式架构。因此微服务来啦！！！</p><h2 id="②微服务简介"><a href="#②微服务简介" class="headerlink" title="②微服务简介"></a>②微服务简介</h2><p><strong>微服务的架构特征：</strong></p><ul><li>单一职责：微服务拆分粒度更小，每一个服务都对应唯一的业务能力，做到单一职责</li><li>自治：团队独立、技术独立、数据独立，独立部署和交付</li><li>面向服务：服务提供统一标准的接口，与语言和技术无关</li><li>隔离性强：服务调用做好隔离、容错、降级，避免出现级联问题</li></ul><p>微服务的上述特性其实是在给分布式架构制定一个标准，进一步降低服务之间的耦合度，提供服务的独立性和灵活性。做到高内聚，低耦合。因此<strong>微服务</strong>是一种经过良好架构设计的<strong>分布式架构方案</strong> 。</p><p><img src="https://img.jwt1399.top/img/202209201355158.png" alt="微服务架构"></p><p>但方案该怎么落地？选用什么样的技术栈？其中在Java领域最引人注目的就是SpringCloud提供的方案了。</p><h2 id="③微服务方案"><a href="#③微服务方案" class="headerlink" title="③微服务方案"></a>③微服务方案</h2><p>目前国内使用最广泛的微服务方案：Dubbo、SpringCloud、SpringCloudAlibaba</p><p><img src="https://img.jwt1399.top/img/202209201416386.png" alt="方案对比"></p><p><img src="https://img.jwt1399.top/img/202209201419610.png"></p><h2 id="⑤案例引入"><a href="#⑤案例引入" class="headerlink" title="⑤案例引入"></a>⑤案例引入</h2><blockquote><p>下方的讲解都基于此案例进行，请提前搭建好此项目。<a href="https://jwt1399.lanzouv.com/b01dymdva">项目cloud-demo</a> 密码:1399</p><p>搭建方式：1.使用IDEA 打开 cloud-demo项目 2.创建两个数据库<code>cloud_order</code>和<code>cloud_user </code>3.将提供的<code>cloud-order.sql</code>和<code>cloud-user.sql</code>导入对应库中 4.修改项目中数据库密码</p></blockquote><p><strong>项目结构：</strong></p><pre class="line-numbers language-bash"><code class="language-bash">cloud-demo  <span class="token comment" spellcheck="true"># 父工程，管理依赖</span>├── order-service <span class="token comment" spellcheck="true"># 订单微服务，负责订单相关业务</span>└── user-service  <span class="token comment" spellcheck="true"># 用户微服务，负责用户相关业务</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p><strong>项目特征：</strong></p><ul><li>订单微服务和用户微服务有各自的数据库，相互独立</li><li>订单服务和用户服务都对外暴露Restful的接口</li><li>订单服务如果需要查询用户信息，只能调用用户服务的Restful接口，不能查询用户数据库</li></ul><p><strong>基础概念：</strong></p><p>在服务调用关系中，会有两个不同的角色：</p><ul><li><p><strong>服务提供者</strong>：一次业务中，被其它微服务调用的服务。（提供接口给其它微服务）</p></li><li><p><strong>服务消费者</strong>：一次业务中，调用其它微服务的服务。（调用其它微服务提供的接口）</p></li><li><p>服务提供者与服务消费者的角色并不是绝对的，而是相对于业务而言。</p></li></ul><p><img src="https://img.jwt1399.top/img/202209202043982.png"></p><p><strong>目前需求：</strong>修改order-service中的查询订单业务，要求在查询订单的同时，根据订单中包含的userId查询出用户信息，一起返回。</p><p><img src="https://img.jwt1399.top/img/202209202020487.png"></p><h1 id="2-服务远程调用①"><a href="#2-服务远程调用①" class="headerlink" title="2.服务远程调用①"></a>2.服务远程调用①</h1><h2 id="①RestTemplate"><a href="#①RestTemplate" class="headerlink" title="①RestTemplate"></a>①RestTemplate</h2><p>要实现上方需求，我们需要在order-service中向user-service发起一个http的请求，调用<code>http://localhost:8081/user/&#123;userId&#125;</code>接口。</p><p>步骤如下：</p><ul><li>注册一个RestTemplate的实例到Spring容器</li><li>修改order-service服务中的OrderService类中的queryOrderById方法，根据Order对象中的userId查询User</li><li>将查询的User填充到Order对象，一起返回</li></ul><p><strong>步骤一：注册RestTemplate</strong></p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@MapperScan</span><span class="token punctuation">(</span><span class="token string">"cn.xxxx.order.mapper"</span><span class="token punctuation">)</span><span class="token annotation punctuation">@SpringBootApplication</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">OrderApplication</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>        SpringApplication<span class="token punctuation">.</span><span class="token function">run</span><span class="token punctuation">(</span>OrderApplication<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">,</span> args<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@Bean</span>    <span class="token keyword">public</span> RestTemplate <span class="token function">restTemplate</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">RestTemplate</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤二：实现远程调用</strong></p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Service</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">OrderService</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Autowired</span>    <span class="token keyword">private</span> OrderMapper orderMapper<span class="token punctuation">;</span>      <span class="token annotation punctuation">@Autowired</span>    <span class="token keyword">private</span> RestTemplate restTemplate     <span class="token keyword">public</span> Order <span class="token function">queryOrderById</span><span class="token punctuation">(</span>Long orderId<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 1.查询订单</span>        Order order <span class="token operator">=</span> orderMapper<span class="token punctuation">.</span><span class="token function">findById</span><span class="token punctuation">(</span>orderId<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 2.远程查询user</span>        <span class="token comment" spellcheck="true">// 2.1 url地址</span>        String url <span class="token operator">=</span> <span class="token string">"http://localhost:8081/user/"</span> <span class="token operator">+</span> order<span class="token punctuation">.</span><span class="token function">getUserId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 2.2 发起调用</span>        User user <span class="token operator">=</span> restTemplate<span class="token punctuation">.</span><span class="token function">getForObject</span><span class="token punctuation">(</span>url<span class="token punctuation">,</span> User<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 3.存入order</span>        order<span class="token punctuation">.</span><span class="token function">setUser</span><span class="token punctuation">(</span>user<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 4.返回</span>        <span class="token keyword">return</span> order<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h1 id="3-服务注册发现"><a href="#3-服务注册发现" class="headerlink" title="3.服务注册发现"></a>3.服务注册发现</h1><h2 id="①Eureka"><a href="#①Eureka" class="headerlink" title="①Eureka"></a>①Eureka</h2><blockquote><p>假如我们的服务提供者user-service部署了多个实例，如图：</p><img src="https://img.jwt1399.top/img/202209202108940.png" style="zoom: 25%;" /><p>思考几个问题：</p><ul><li>order-service在发起远程调用的时候，该如何得知user-service实例的ip地址和端口？</li><li>有多个user-service实例地址，order-service调用时该如何选择？</li><li>order-service如何得知某个user-service实例是否依然健康，是不是已经宕机？</li></ul></blockquote><h3 id="❶Eureka原理分析"><a href="#❶Eureka原理分析" class="headerlink" title="❶Eureka原理分析"></a>❶Eureka原理分析</h3><p>这些问题都需要利用SpringCloud中的注册中心来解决，其中最广为人知的注册中心就是Eureka，其结构如下：</p><p><img src="https://img.jwt1399.top/img/202209202112546.png"></p><p>问题1：order-service如何得知user-service实例地址？</p><ul><li>user-service服务实例启动后，将自己的信息注册到eureka-server（Eureka服务端）。这个叫服务注册</li><li>eureka-server保存服务名称到服务实例地址列表的映射关系</li><li>order-service根据服务名称，拉取实例地址列表。这个叫服务发现或服务拉取</li></ul><p>问题2：order-service如何从多个user-service实例中选择具体的实例？</p><ul><li>order-service从实例列表中利用负载均衡算法选中一个实例地址</li><li>并向该实例地址发起远程调用</li></ul><p>问题3：order-service如何得知某个user-service实例是否依然健康，是不是已经宕机？</p><ul><li>user-service会每隔一段时间（默认30秒）向eureka-server发起请求，报告自己状态，称为心跳</li><li>当超过一定时间没有发送心跳时，eureka-server会认为微服务实例故障，将该实例从服务列表中剔除</li><li>order-service拉取服务时，就能将故障实例排除了</li></ul><p><img src="https://img.jwt1399.top/img/202209202118474.png" alt="使用步骤"></p><h3 id="❷搭建eureka-server"><a href="#❷搭建eureka-server" class="headerlink" title="❷搭建eureka-server"></a>❷搭建eureka-server</h3><p><strong>步骤一：创建eureka-server模块</strong></p><p>在cloud-demo父工程下，创建一个maven子模块 eureka-server</p><p><strong>步骤二：引入eureka依赖</strong></p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework.cloud<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-cloud-starter-netflix-eureka-server<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤三：编写启动类</strong></p><p>编写一个启动类，添加<code>@EnableEurekaServer</code>注解，开启eureka的注册中心功能</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@EnableEurekaServer</span><span class="token annotation punctuation">@SpringBootApplication</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">EurekaApplication</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>        SpringApplication<span class="token punctuation">.</span><span class="token function">run</span><span class="token punctuation">(</span>EurekaApplication<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">,</span> args<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤四：编写配置文件</strong></p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">server</span><span class="token punctuation">:</span>  <span class="token key atrule">port</span><span class="token punctuation">:</span> <span class="token number">10086</span><span class="token key atrule">spring</span><span class="token punctuation">:</span>  <span class="token key atrule">application</span><span class="token punctuation">:</span>    <span class="token key atrule">name</span><span class="token punctuation">:</span> eureka<span class="token punctuation">-</span>server<span class="token key atrule">eureka</span><span class="token punctuation">:</span>  <span class="token key atrule">client</span><span class="token punctuation">:</span>    <span class="token key atrule">service-url</span><span class="token punctuation">:</span>       <span class="token key atrule">defaultZone</span><span class="token punctuation">:</span> http<span class="token punctuation">:</span>//127.0.0.1<span class="token punctuation">:</span>10086/eureka<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤五：启动服务</strong></p><p>启动微服务，然后在浏览器访问：<code>http://127.0.0.1:10086</code>，看到Web页面表示成功了</p><h3 id="❸服务注册"><a href="#❸服务注册" class="headerlink" title="❸服务注册"></a>❸服务注册</h3><p>下面，我们将user-service注册到eureka-server中去。</p><p><strong>步骤一：引入依赖</strong></p><p>在user-service的pom文件中，引入<code>eureka-client</code>依赖：</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework.cloud<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-cloud-starter-netflix-eureka-client<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤二：配置文件</strong></p><p>在user-service中，修改application.yml文件，添加服务名称、eureka地址：</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">spring</span><span class="token punctuation">:</span>  <span class="token key atrule">application</span><span class="token punctuation">:</span>    <span class="token key atrule">name</span><span class="token punctuation">:</span> userservice<span class="token key atrule">eureka</span><span class="token punctuation">:</span>  <span class="token key atrule">client</span><span class="token punctuation">:</span>    <span class="token key atrule">service-url</span><span class="token punctuation">:</span>      <span class="token key atrule">defaultZone</span><span class="token punctuation">:</span> http<span class="token punctuation">:</span>//127.0.0.1<span class="token punctuation">:</span>10086/eureka<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤三：启动多个user-service实例</strong></p><p>为了演示一个服务有多个实例的场景，可以通过SpringBoot的启动配置，再启动一个user-service。</p><table><thead><tr><th>1.复制原来的user-service启动配置：</th><th>2.在弹出的窗口中，填写信息：</th></tr></thead><tbody><tr><td><img src="https://img.jwt1399.top/img/202209211240292.png"></td><td><img src="https://img.jwt1399.top/img/202209211241706.png" style="zoom:50%;" /></td></tr><tr><td><strong>3.出现两个user-service实例</strong></td><td><strong>4.启动两个user-service实例</strong></td></tr><tr><td><img src="https://img.jwt1399.top/img/202209211241315.png"></td><td><img src="https://img.jwt1399.top/img/202209211241522.png"></td></tr></tbody></table><p>查看eureka-server管理页面：</p><p><img src="https://img.jwt1399.top/img/202209211241215.png"></p><h3 id="❹服务发现"><a href="#❹服务发现" class="headerlink" title="❹服务发现"></a>❹服务发现</h3><p>下面，我们将order-service的逻辑修改：实现向eureka-server拉取user-service的信息，即服务发现。</p><p><strong>步骤一：引入依赖</strong></p><p>服务发现、服务注册统一都封装在eureka-client依赖，因此这一步与服务注册时一致。</p><p>在order-service的pom文件中，引入eureka-client依赖：</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework.cloud<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-cloud-starter-netflix-eureka-client<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤二：配置文件</strong></p><p>服务发现也需要知道eureka地址，在order-service中，修改application.yml文件，添加服务名称、eureka地址：</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">spring</span><span class="token punctuation">:</span>  <span class="token key atrule">application</span><span class="token punctuation">:</span>    <span class="token key atrule">name</span><span class="token punctuation">:</span> orderservice<span class="token key atrule">eureka</span><span class="token punctuation">:</span>  <span class="token key atrule">client</span><span class="token punctuation">:</span>    <span class="token key atrule">service-url</span><span class="token punctuation">:</span>      <span class="token key atrule">defaultZone</span><span class="token punctuation">:</span> http<span class="token punctuation">:</span>//127.0.0.1<span class="token punctuation">:</span>10086/eureka<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤三：服务拉取和负载均衡</strong></p><p>最后，我们要去eureka-server中拉取user-service服务的实例列表，并且实现负载均衡。</p><p>在order-service的OrderApplication中，给RestTemplate这个Bean添加一个@LoadBalanced注解：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@MapperScan</span><span class="token punctuation">(</span><span class="token string">"cn.xxxx.order.mapper"</span><span class="token punctuation">)</span><span class="token annotation punctuation">@SpringBootApplication</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">OrderApplication</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>        SpringApplication<span class="token punctuation">.</span><span class="token function">run</span><span class="token punctuation">(</span>OrderApplication<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">,</span> args<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@Bean</span>    <span class="token annotation punctuation">@LoadBalanced</span> <span class="token comment" spellcheck="true">//添加注解</span>    <span class="token keyword">public</span> RestTemplate <span class="token function">restTemplate</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">RestTemplate</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>修改order-service服务中的OrderService类中的queryOrderById方法。修改访问的url路径，用服务名代替ip、端口：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Service</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">OrderService</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Autowired</span>    <span class="token keyword">private</span> OrderMapper orderMapper<span class="token punctuation">;</span>      <span class="token annotation punctuation">@Autowired</span>    <span class="token keyword">private</span> RestTemplate restTemplate     <span class="token keyword">public</span> Order <span class="token function">queryOrderById</span><span class="token punctuation">(</span>Long orderId<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 1.查询订单</span>        Order order <span class="token operator">=</span> orderMapper<span class="token punctuation">.</span><span class="token function">findById</span><span class="token punctuation">(</span>orderId<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 2.远程查询user</span>        <span class="token comment" spellcheck="true">// 2.1 url地址</span>                <span class="token comment" spellcheck="true">//String url = "http://localhost:8081/user/" + order.getUserId();</span>        String url <span class="token operator">=</span> <span class="token string">"http://userservice/user/"</span> <span class="token operator">+</span> order<span class="token punctuation">.</span><span class="token function">getUserId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 2.2 发起调用</span>        User user <span class="token operator">=</span> restTemplate<span class="token punctuation">.</span><span class="token function">getForObject</span><span class="token punctuation">(</span>url<span class="token punctuation">,</span> User<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 3.存入order</span>        order<span class="token punctuation">.</span><span class="token function">setUser</span><span class="token punctuation">(</span>user<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 4.返回</span>        <span class="token keyword">return</span> order<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>spring会自动帮助我们从eureka-server端，根据userservice这个服务名称，获取实例列表，而后完成负载均衡。</p><h3 id="❺总结"><a href="#❺总结" class="headerlink" title="❺总结"></a>❺总结</h3><ul><li><p>1.搭建EurekaServer</p><ul><li><p>引入eureka-server依赖</p></li><li><p>添加@EnableEurekaServer注解</p></li><li><p>在application.yml中配置eureka地址</p></li></ul></li><li><p>2.服务注册</p><ul><li>引入eureka-client依赖</li><li>在application.yml中配置eureka地址</li></ul></li><li><p>3.服务发现</p><ul><li>引入eureka-client依赖</li><li>在application.yml中配置eureka地址</li><li>给RestTemplate添加@LoadBalanced注解</li><li>用服务提供者的服务名称远程调用</li></ul></li></ul><h2 id="②Nacos"><a href="#②Nacos" class="headerlink" title="②Nacos"></a>②Nacos</h2><blockquote><p><a href="https://nacos.io/">Nacos</a>是阿里巴巴的产品，是 SpringCloudAlibaba 中的一个组件。相比 Eureka 功能更加丰富，在国内受欢迎程度较高。</p></blockquote><h3 id="❶安装"><a href="#❶安装" class="headerlink" title="❶安装"></a>❶安装</h3><p>下载地址：<a href="https://github.com/alibaba/nacos">https://github.com/alibaba/nacos</a></p><p>下载后解压即可，目录说明：</p><pre><code>- log           nacos生成日志说明- bin           nacos服务相关脚本目录，- conf          nacos的配置文件目录- target        nacos的启动依赖目录- data          nacos自带apache-derby数据库，data存放数据内容</code></pre><pre class="line-numbers language-bash"><code class="language-bash"><span class="token comment" spellcheck="true"># 启动命令 单体启动</span>sh startup.sh -m standalone<span class="token comment" spellcheck="true"># 关闭命令</span>sh shutdown.sh<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>在浏览器输入地址即可访问：<code>http://127.0.0.1:8848/nacos</code>，默认的账号和密码都是nacos</p><p>如果你打不开这个网页 ！多半是版本问题。</p><h3 id="❷服务注册-x2F-发现"><a href="#❷服务注册-x2F-发现" class="headerlink" title="❷服务注册&#x2F;发现"></a>❷服务注册&#x2F;发现</h3><p><strong>步骤一：引入依赖</strong></p><p>在cloud-demo父工程的pom文件中的<code>&lt;dependencyManagement&gt;</code>中引入SpringCloudAlibaba的依赖：</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>com.alibaba.cloud<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-cloud-alibaba-dependencies<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>2.2.6.RELEASE<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>type</span><span class="token punctuation">></span></span>pom<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>type</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>scope</span><span class="token punctuation">></span></span>import<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>scope</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>然后在user-service和order-service中的pom文件中引入nacos-discovery依赖：</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>com.alibaba.cloud<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-cloud-starter-alibaba-nacos-discovery<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><blockquote><p><strong>注意</strong>：不要忘了注释掉eureka的依赖。</p></blockquote><p><strong>步骤二：配置nacos地址</strong></p><p>在user-service和order-service的application.yml中添加nacos地址：</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">spring</span><span class="token punctuation">:</span>  <span class="token key atrule">cloud</span><span class="token punctuation">:</span>    <span class="token key atrule">nacos</span><span class="token punctuation">:</span>      <span class="token key atrule">server-addr</span><span class="token punctuation">:</span> localhost<span class="token punctuation">:</span><span class="token number">8848</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><blockquote><p><strong>注意</strong>：不要忘了注释掉eureka的地址</p></blockquote><p><strong>步骤三：重启</strong></p><p>重启微服务后，登录nacos管理页面，可以看到微服务信息</p><p><img src="https://img.jwt1399.top/img/202209211820247.png"></p><h3 id="❸集群配置"><a href="#❸集群配置" class="headerlink" title="❸集群配置"></a>❸集群配置</h3><h4 id="⓵分级存储模型"><a href="#⓵分级存储模型" class="headerlink" title="⓵分级存储模型"></a>⓵分级存储模型</h4><p>一个<strong>服务</strong>可以有多个<strong>实例</strong>，例如我们的user-service，假如这些实例分布于全国各地的不同机房</p><table><thead><tr><th>实例</th><th>机房</th></tr></thead><tbody><tr><td>127.0.0.1:8081</td><td>杭州</td></tr><tr><td>127.0.0.1:8082</td><td>杭州</td></tr><tr><td>127.0.0.1:8083</td><td>上海</td></tr></tbody></table><p>Nacos就将同一机房内的实例划分为一个<strong>集群</strong>。也就是说，user-service是服务，一个服务可以包含多个集群，如杭州、上海、北京，每个集群下可以有多个实例，形成分级模型，如图：</p><p><img src="https://img.jwt1399.top/img/202209211844749.png"></p><p>①一级是服务，例如user-service；②二级是集群，例如杭州或上海；③三级是实例，例如杭州机房的某台部署</p><p>微服务互相访问时，应该尽可能访问同集群实例，因为本地访问速度更快。当本集群内实例不可用时，才访问其它集群。例如：杭州机房内的order-service应该优先访问同机房的user-service。</p><p><img src="https://img.jwt1399.top/img/202209211845713.png"></p><h4 id="⓶给user-service配置集群"><a href="#⓶给user-service配置集群" class="headerlink" title="⓶给user-service配置集群"></a>⓶给user-service配置集群</h4><p>修改<code>user-service</code>的application.yml文件，添加集群配置：</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">spring</span><span class="token punctuation">:</span>  <span class="token key atrule">cloud</span><span class="token punctuation">:</span>    <span class="token key atrule">nacos</span><span class="token punctuation">:</span>      <span class="token key atrule">server-addr</span><span class="token punctuation">:</span> localhost<span class="token punctuation">:</span><span class="token number">8848</span>      <span class="token key atrule">discovery</span><span class="token punctuation">:</span>        <span class="token key atrule">cluster-name</span><span class="token punctuation">:</span> HZ <span class="token comment" spellcheck="true"># 集群名称</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>重启两个user-service实例后，我们可以在nacos控制台看到下面结果：</p><p><img src="/../images/SpringCloud-%E5%AE%9E%E7%94%A8%E7%AF%87/image-20220921185203122.png"></p><p>我们再次复制一个user-service启动配置UserApplication3，复制方式参考【上方搭建eureka-server步骤三】</p><p>添加VM属性：</p><pre class="line-numbers language-sh"><code class="language-sh">-Dserver.port=8083 -Dspring.cloud.nacos.discovery.cluster-name=SH<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>启动UserApplication3后再次查看nacos控制台：</p><p><img src="https://img.jwt1399.top/img/202209211858088.png"></p><h4 id="⓷同集群优先的负载均衡"><a href="#⓷同集群优先的负载均衡" class="headerlink" title="⓷同集群优先的负载均衡"></a>⓷同集群优先的负载均衡</h4><p>负载均衡默认规则<code>ZoneAvoidanceRule</code>并不能实现根据同集群优先来实现负载均衡。因此Nacos中提供了一个<code>NacosRule</code>的实现，可以优先从同集群中挑选实例。</p><p><strong>步骤一：给order-service配置集群信息</strong></p><p>修改<code>order-service</code>的application.yml文件，添加集群配置：</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">spring</span><span class="token punctuation">:</span>  <span class="token key atrule">cloud</span><span class="token punctuation">:</span>    <span class="token key atrule">nacos</span><span class="token punctuation">:</span>      <span class="token key atrule">server-addr</span><span class="token punctuation">:</span> localhost<span class="token punctuation">:</span><span class="token number">8848</span>      <span class="token key atrule">discovery</span><span class="token punctuation">:</span>        <span class="token key atrule">cluster-name</span><span class="token punctuation">:</span> HZ <span class="token comment" spellcheck="true"># 集群名称</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤二：修改负载均衡规则</strong></p><p>修改<code>order-service</code>的application.yml文件，修改负载均衡规则：</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">userservice</span><span class="token punctuation">:</span>  <span class="token key atrule">ribbon</span><span class="token punctuation">:</span>    <span class="token key atrule">NFLoadBalancerRuleClassName</span><span class="token punctuation">:</span> com.alibaba.cloud.nacos.ribbon.NacosRule <span class="token comment" spellcheck="true"># 负载均衡规则 </span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>NacosRule负载均衡策略</p><ul><li><p>①优先选择同集群服务实例列表</p></li><li><p>②本地集群找不到提供者，才去其它集群寻找，并且会报警告</p></li><li><p>③确定了可用实例列表后，再采用随机负载均衡挑选实例</p></li></ul><h3 id="❹权重配置"><a href="#❹权重配置" class="headerlink" title="❹权重配置"></a>❹权重配置</h3><p><strong>实际部署中会出现这样的场景：</strong>服务器设备性能有差异，部分实例所在机器性能较好，另一些较差，我们希望性能好的机器承担更多的用户请求。但默认情况下NacosRule是同集群内随机挑选，不会考虑机器的性能问题。</p><p>因此，Nacos提供了权重配置来控制访问频率，权重越大则访问频率越高。在nacos控制台，找到user-service的实例列表，点击编辑，即可修改权重：</p><table><thead><tr><th><img src="https://img.jwt1399.top/img/202209211916270.png"></th><th><img src="https://img.jwt1399.top/img/202209211916058.png"></th></tr></thead></table><blockquote><p><strong>注意</strong>：如果权重修改为0，则该实例永远不会被访问，使用场景：进行实例的平滑更新</p></blockquote><h3 id="❺环境隔离"><a href="#❺环境隔离" class="headerlink" title="❺环境隔离"></a>❺环境隔离</h3><p>Nacos提供了namespace来实现环境隔离功能。</p><ul><li>nacos中可以有多个namespace</li><li>namespace下可以有group、service等</li><li>不同namespace之间相互隔离，例如不同namespace的服务互相不可见</li></ul><p><strong>步骤一：创建namespace</strong></p><p>默认情况下，所有service、group、data都在同一个namespace，名为public</p><p><img src="https://img.jwt1399.top/img/202209211923602.png"></p><table><thead><tr><th>添加一个namespace</th><th>设置信息</th></tr></thead><tbody><tr><td><img src="https://img.jwt1399.top/img/202209211922322.png"></td><td><img src="https://img.jwt1399.top/img/202209211922404.png"></td></tr></tbody></table><p>就能在页面看到一个新的namespace：</p><p><img src="https://img.jwt1399.top/img/202209211923581.png"></p><p><strong>步骤二：给微服务配置namespace</strong></p><p>给微服务配置namespace只能通过修改配置来实现。例如，修改order-service的application.yml文件：</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">spring</span><span class="token punctuation">:</span>  <span class="token key atrule">cloud</span><span class="token punctuation">:</span>    <span class="token key atrule">nacos</span><span class="token punctuation">:</span>      <span class="token key atrule">server-addr</span><span class="token punctuation">:</span> localhost<span class="token punctuation">:</span><span class="token number">8848</span>      <span class="token key atrule">discovery</span><span class="token punctuation">:</span>        <span class="token key atrule">cluster-name</span><span class="token punctuation">:</span> HZ        <span class="token key atrule">namespace</span><span class="token punctuation">:</span> 492a7d5d<span class="token punctuation">-</span>237b<span class="token punctuation">-</span>46a1<span class="token punctuation">-</span>a99a<span class="token punctuation">-</span>fa8e98e4b0f9 <span class="token comment" spellcheck="true"># 命名空间，填ID</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>重启order-service后，访问控制台，可以看到下面的结果：</p><table><thead><tr><th><img src="https://img.jwt1399.top/img/202209211924994.png"></th><th><img src="https://img.jwt1399.top/img/202209211924277.png"></th></tr></thead></table><p>此时访问order-service，因为namespace不同，会导致找不到userservice，控制台会报错：</p><p><img src="https://img.jwt1399.top/img/202209211925591.png"></p><h2 id="③Nacos与Eureka区别"><a href="#③Nacos与Eureka区别" class="headerlink" title="③Nacos与Eureka区别"></a>③Nacos与Eureka区别</h2><p>Nacos的服务实例分为两种类型：</p><ul><li><p>临时实例：    如果实例宕机超过一定时间，会从服务列表剔除，默认的实例类型。</p></li><li><p>非临时实例：如果实例宕机，不会从服务列表剔除，也可以叫永久实例。</p></li></ul><p>配置一个服务实例为永久实例：</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">spring</span><span class="token punctuation">:</span>  <span class="token key atrule">cloud</span><span class="token punctuation">:</span>    <span class="token key atrule">nacos</span><span class="token punctuation">:</span>      <span class="token key atrule">discovery</span><span class="token punctuation">:</span>        <span class="token key atrule">ephemeral</span><span class="token punctuation">:</span> <span class="token boolean important">false </span><span class="token comment" spellcheck="true"># 设置为非临时实例</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>Nacos和Eureka整体结构类似，服务注册、服务拉取、心跳等待，但是也存在一些差异：</p><p><img src="https://img.jwt1399.top/img/202209211928061.png"></p><table><thead><tr><th>共同点</th><th>不同点</th></tr></thead><tbody><tr><td>①都支持服务注册和服务拉取<br>②都支持服务提供者心跳方式做健康检测</td><td>①Nacos支持服务端主动检测提供者状态，临时实例采用心跳模式，非临时实例采用主动检测模式 <br/>②临时实例心跳不正常会被剔除，非临时实例则不会被剔除<br/>③Nacos支持服务列表变更的消息推送模式，服务列表更新更及时<br/>④Nacos集群默认采用AP方式，当集群中存在非临时实例时，采用CP模式；Eureka采用AP方式</td></tr></tbody></table><h1 id="4-负载均衡-Ribbon"><a href="#4-负载均衡-Ribbon" class="headerlink" title="4.负载均衡-Ribbon"></a>4.负载均衡-Ribbon</h1><h2 id="①负载均衡原理"><a href="#①负载均衡原理" class="headerlink" title="①负载均衡原理"></a>①负载均衡原理</h2><p>SpringCloud底层利用Ribbon来实现负载均衡功能</p><p><img src="https://img.jwt1399.top/img/202209211319306.png"></p><p>我们发出的请求明明是<code>http://userservice/user/1</code>，怎么变成了<code>http://localhost:8081/user/1</code>的呢？</p><blockquote><p>显然有人帮我们根据service名称，获取到了服务实例的ip和端口。它就是<code>LoadBalancerInterceptor</code>，这个类会在对RestTemplate的请求进行拦截，然后从Eureka根据服务id获取服务列表，随后利用负载均衡算法得到真实的服务地址信息，替换服务id。我们进行源码跟踪：</p></blockquote><h3 id="❶LoadBalancerIntercepor"><a href="#❶LoadBalancerIntercepor" class="headerlink" title="❶LoadBalancerIntercepor"></a>❶LoadBalancerIntercepor</h3><p><img src="https://img.jwt1399.top/img/202209211333296.png"></p><p>可以看到这里的<code>intercept</code>方法，拦截了用户的HttpRequest请求，然后做了几件事：</p><ul><li><code>request.getURI()</code>：获取请求uri，本例中就是 <code>http://user-service/user/8</code></li><li><code>originalUri.getHost()</code>：获取uri路径的主机名，其实就是服务id，<code>user-service</code></li><li><code>this.loadBalancer.execute()</code>：处理服务id，和用户请求。</li></ul><p>这里的<code>this.loadBalancer</code>是<code>LoadBalancerClient</code>类型，我们继续跟入execute方法：</p><h3 id="❷LoadBalancerClient"><a href="#❷LoadBalancerClient" class="headerlink" title="❷LoadBalancerClient"></a>❷LoadBalancerClient</h3><p><img src="https://img.jwt1399.top/img/202209211333636.png"></p><ul><li><code>getLoadBalancer(serviceId)</code>：根据服务id获取ILoadBalancer，而ILoadBalancer会拿着服务id去eureka中获取服务列表并保存起来。</li><li><code>getServer(loadBalancer)</code>：利用内置的负载均衡算法，从服务列表中选择一个。本例中，可以看到获取了8082端口的服务</li></ul><p>放行后，再次访问并跟踪，发现获取的是8081，果然实现了负载均衡。</p><p> <img src="https://img.jwt1399.top/img/202209211333458.png"></p><h3 id="❸负载均衡策略IRule"><a href="#❸负载均衡策略IRule" class="headerlink" title="❸负载均衡策略IRule"></a>❸负载均衡策略IRule</h3><p>在刚才的代码中，可以看到获取服务使通过一个<code>getServer</code>方法来做负载均衡:</p><p><img src="https://img.jwt1399.top/img/202209211333458.png"></p><p>我们继续跟入：</p><p><img src="https://img.jwt1399.top/img/202209211337777.png"></p><p>继续跟踪源码chooseServer方法，发现这么一段代码：</p><table><thead><tr><th><img src="https://img.jwt1399.top/img/202209211337640.png"></th><th><img src="https://img.jwt1399.top/img/202209211337661.png"></th></tr></thead></table><p>这里的rule默认值是一个<code>RoundRobinRule</code>，看类的介绍：</p><p> <img src="https://img.jwt1399.top/img/202209211339083.png"></p><p>这不就是轮询的意思嘛。到这里，整个负载均衡的流程我们就清楚了。</p><h3 id="❹总结"><a href="#❹总结" class="headerlink" title="❹总结"></a>❹总结</h3><p>Ribbon的底层采用了一个拦截器，拦截了RestTemplate发出的请求，对地址做了修改。用一幅图来总结一下：</p><p><img src="https://img.jwt1399.top/img/202209211340322.png"></p><p>基本流程如下：</p><ul><li>1.拦截我们的<strong>RestTemplate</strong>请求<code>http://userservice/user/1</code></li><li>2.<strong>RibbonLoadBalancerClient</strong>会从请求url中获取服务名称，也就是userservice</li><li>3.<strong>DynamicServerListLoadBalancer</strong>根据userservice到eureka拉取服务列表</li><li>4.<strong>eureka-server</strong>返回列表，localhost:8081、localhost:8082</li><li>5.<strong>IRule</strong>利用内置负载均衡规则，从列表中选择一个，例如localhost:8081</li><li>6.<strong>RibbonLoadBalancerClient</strong>修改请求地址，用localhost:8081替代userservice，得到<code>http://localhost:8081/user/1</code>，发起真实请求</li></ul><h2 id="②负载均衡策略"><a href="#②负载均衡策略" class="headerlink" title="②负载均衡策略"></a>②负载均衡策略</h2><p>负载均衡的规则都定义在IRule接口中，而IRule有很多不同的实现类：</p><p><img src="https://img.jwt1399.top/img/202209211344655.png"></p><p><strong>不同规则的含义如下：</strong></p><table><thead><tr><th><strong>内置负载均衡规则类</strong></th><th><strong>规则描述</strong></th></tr></thead><tbody><tr><td>RoundRobinRule</td><td>简单轮询服务列表来选择服务器。它是Ribbon默认的负载均衡规则。</td></tr><tr><td>AvailabilityFilteringRule</td><td>对以下两种服务器进行忽略：   （1）在默认情况下，这台服务器如果3次连接失败，这台服务器就会被设置为“短路”状态。短路状态将持续30秒，如果再次连接失败，短路的持续时间就会几何级地增加。  （2）并发数过高的服务器。如果一个服务器的并发连接数过高，配置了AvailabilityFilteringRule规则的客户端也会将其忽略。并发连接数的上限，可以由客户端的&lt;clientName&gt;.&lt;clientConfigNameSpace&gt;.ActiveConnectionsLimit属性进行配置。</td></tr><tr><td>WeightedResponseTimeRule</td><td>为每一个服务器赋予一个权重值。服务器响应时间越长，这个服务器的权重就越小。这个规则会随机选择服务器，这个权重值会影响服务器的选择。</td></tr><tr><td><strong>ZoneAvoidanceRule</strong></td><td>以区域可用的服务器为基础进行服务器的选择。使用Zone对服务器进行分类，这个Zone可以理解为一个机房、一个机架等。而后再对Zone内的多个服务做轮询。</td></tr><tr><td>BestAvailableRule</td><td>忽略那些短路的服务器，并选择并发数较低的服务器。</td></tr><tr><td>RandomRule</td><td>随机选择一个可用的服务器。</td></tr><tr><td>RetryRule</td><td>重试机制的选择逻辑</td></tr></tbody></table><p>默认的实现就是ZoneAvoidanceRule，是一种轮询方案</p><p><strong>自定义负载均衡策略</strong></p><p>通过定义IRule实现可以修改负载均衡规则，有两种方式：</p><ul><li>1.全局配置：在order-service中的OrderApplication类中，定义一个新的IRule</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Bean</span><span class="token keyword">public</span> IRule <span class="token function">randomRule</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>    <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">RandomRule</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><ul><li>2.单个配置：在order-service的application.yml文件中，添加新的配置也可以修改规则：</li></ul><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">userservice</span><span class="token punctuation">:</span> <span class="token comment" spellcheck="true"># 给某个微服务配置负载均衡规则，这里是userservice服务</span>  <span class="token key atrule">ribbon</span><span class="token punctuation">:</span>    <span class="token key atrule">NFLoadBalancerRuleClassName</span><span class="token punctuation">:</span> com.netflix.loadbalancer.RandomRule <span class="token comment" spellcheck="true"># 负载均衡规则 </span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><blockquote><p><strong>注意</strong>，一般用默认的负载均衡规则，不做修改。</p></blockquote><h2 id="③饥饿加载"><a href="#③饥饿加载" class="headerlink" title="③饥饿加载"></a>③饥饿加载</h2><p>Ribbon默认是采用懒加载，即第一次访问时才会去创建LoadBalanceClient，请求时间会很长。</p><p>而饥饿加载则会在项目启动时创建，降低第一次访问的耗时，通过下面配置开启饥饿加载：</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">ribbon</span><span class="token punctuation">:</span>  <span class="token key atrule">eager-load</span><span class="token punctuation">:</span>    <span class="token key atrule">enabled</span><span class="token punctuation">:</span> <span class="token boolean important">true</span>    <span class="token key atrule">clients</span><span class="token punctuation">:</span> userservice<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><h1 id="5-服务远程调用②"><a href="#5-服务远程调用②" class="headerlink" title="5.服务远程调用②"></a>5.服务远程调用②</h1><h2 id="②OpenFeign"><a href="#②OpenFeign" class="headerlink" title="②OpenFeign"></a>②OpenFeign</h2><blockquote><p>前面利用RestTemplate发起远程调用的代码</p><pre class="line-numbers language-java"><code class="language-java">String url <span class="token operator">=</span> <span class="token string">"http://localhost:8081/user/"</span> <span class="token operator">+</span> order<span class="token punctuation">.</span><span class="token function">getUserId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>User user <span class="token operator">=</span> restTemplate<span class="token punctuation">.</span><span class="token function">getForObject</span><span class="token punctuation">(</span>url<span class="token punctuation">,</span> User<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>存在下面的问题：</p><ul><li><p>代码可读性差，编程体验不统一</p></li><li><p>参数复杂URL难以维护</p></li></ul></blockquote><p>OpenFeign是一个声明式的http客户端，其作用就是帮助我们优雅的实现http请求的发送，解决上面提到的问题。</p><h3 id="❶使用"><a href="#❶使用" class="headerlink" title="❶使用"></a>❶使用</h3><p><strong>步骤一：引入依赖</strong></p><p>在order-service服务的pom文件中引入openfeign的依赖</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework.cloud<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-cloud-starter-openfeign<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>使用 openfeign 依赖 ribbon，但是最新版的 openfeign 移除了ribbon ，因此如果单独使用openfeign需要引入ribbon，并进行配置。如果配合 eureka 或 nacos 则不用</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework.cloud<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-cloud-starter-netflix-ribbon<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token comment" spellcheck="true"># 这个userservice是加了@FeignClient注解的类的name</span><span class="token key atrule">userservice</span><span class="token punctuation">:</span>  <span class="token key atrule">ribbon</span><span class="token punctuation">:</span>    <span class="token comment" spellcheck="true"># 服务提供者的地址，不是服务注册中心的地址</span>    <span class="token key atrule">listOfServers</span><span class="token punctuation">:</span> http<span class="token punctuation">:</span>//localhost<span class="token punctuation">:</span><span class="token number">8081</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤二：添加注解</strong></p><p>在order-service的启动类添加注解开启openfeign的功能：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@EnableFeignClients</span> <span class="token comment" spellcheck="true">//开启feign</span><span class="token annotation punctuation">@MapperScan</span><span class="token punctuation">(</span><span class="token string">"cn.xxxx.order.mapper"</span><span class="token punctuation">)</span><span class="token annotation punctuation">@SpringBootApplication</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">OrderApplication</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>        SpringApplication<span class="token punctuation">.</span><span class="token function">run</span><span class="token punctuation">(</span>OrderApplication<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">,</span> args<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤三：编写Feign的客户端</strong></p><p>在order-service中新建一个接口，内容如下：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@FeignClient</span><span class="token punctuation">(</span><span class="token string">"userservice"</span><span class="token punctuation">)</span><span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">UserClient</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@GetMapping</span><span class="token punctuation">(</span><span class="token string">"/user/{id}"</span><span class="token punctuation">)</span>    User <span class="token function">findById</span><span class="token punctuation">(</span><span class="token annotation punctuation">@PathVariable</span><span class="token punctuation">(</span><span class="token string">"id"</span><span class="token punctuation">)</span> Long id<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>这个客户端主要是基于SpringMVC的注解来声明远程调用的信息，比如：</p><ul><li>服务名称：userservice</li><li>请求方式：GET</li><li>请求路径：&#x2F;user&#x2F;{id}</li><li>请求参数：Long id</li><li>返回值类型：User</li></ul><p>这样，Feign就可以帮助我们发送http请求，无需自己使用RestTemplate来发送了。</p><p><strong>步骤四：实现远程调用</strong></p><p>修改order-service中的OrderService类中的queryOrderById方法，使用Feign客户端代替RestTemplate：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Service</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">OrderService</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Autowired</span>    <span class="token keyword">private</span> OrderMapper orderMapper<span class="token punctuation">;</span>      <span class="token annotation punctuation">@Autowired</span>    <span class="token keyword">private</span> UserClient userClient     <span class="token keyword">public</span> Order <span class="token function">queryOrderById</span><span class="token punctuation">(</span>Long orderId<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 1.查询订单</span>        Order order <span class="token operator">=</span> orderMapper<span class="token punctuation">.</span><span class="token function">findById</span><span class="token punctuation">(</span>orderId<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 2.利用Feign发起http请求，查询用户</span>        User user <span class="token operator">=</span> userClient<span class="token punctuation">.</span><span class="token function">findById</span><span class="token punctuation">(</span>order<span class="token punctuation">.</span><span class="token function">getUserId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 3.存入order</span>        order<span class="token punctuation">.</span><span class="token function">setUser</span><span class="token punctuation">(</span>user<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 4.返回</span>        <span class="token keyword">return</span> order<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><img src="https://img.jwt1399.top/img/202209202105075.png" alt="实现过程"></p><p><strong>总结</strong></p><p>使用Feign的步骤：</p><ul><li><p>① 引入依赖</p></li><li><p>② 添加@EnableFeignClients注解</p></li><li><p>③ 编写FeignClient接口</p></li><li><p>④ 使用FeignClient中定义的方法代替RestTemplate</p></li></ul><h3 id="❷配置"><a href="#❷配置" class="headerlink" title="❷配置"></a>❷配置</h3><p>Feign可以支持很多的自定义配置，如下表所示：</p><table><thead><tr><th>类型</th><th>作用</th><th>说明</th></tr></thead><tbody><tr><td><strong>feign.Logger.Level</strong></td><td>修改日志级别</td><td>包含四种不同的级别：NONE、BASIC、HEADERS、FULL</td></tr><tr><td>feign.codec.Decoder</td><td>响应结果的解析器</td><td>http远程调用的结果做解析，例如解析json字符串为java对象</td></tr><tr><td>feign.codec.Encoder</td><td>请求参数编码</td><td>将请求参数编码，便于通过http请求发送</td></tr><tr><td>feign. Contract</td><td>支持的注解格式</td><td>默认是SpringMVC的注解</td></tr><tr><td>feign. Retryer</td><td>失败重试机制</td><td>请求失败的重试机制，默认是没有，不过会使用Ribbon的重试</td></tr></tbody></table><p>日志的级别分为四种：</p><ul><li>NONE：不记录任何日志信息，这是默认值。</li><li>BASIC：仅记录请求的方法，URL以及响应状态码和执行时间（推荐使用）</li><li>HEADERS：在BASIC的基础上，额外记录了请求和响应的头信息</li><li>FULL：记录所有请求和响应的明细，包括头信息、请求体、元数据。</li></ul><p>一般情况下，默认值就能满足我们使用，如果要自定义时，可以通过<strong>修改配置文件</strong>或者创建<strong>自定义@Bean覆盖默认Bean</strong></p><p><strong>修改配置文件方式</strong></p><ul><li>针对单个服务：</li></ul><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">feign</span><span class="token punctuation">:</span>    <span class="token key atrule">client</span><span class="token punctuation">:</span>    <span class="token key atrule">config</span><span class="token punctuation">:</span>       <span class="token key atrule">userservice</span><span class="token punctuation">:</span> <span class="token comment" spellcheck="true"># 针对某个微服务的配置</span>        <span class="token key atrule">loggerLevel</span><span class="token punctuation">:</span> FULL <span class="token comment" spellcheck="true">#  日志级别 </span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>针对所有服务：</li></ul><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">feign</span><span class="token punctuation">:</span>    <span class="token key atrule">client</span><span class="token punctuation">:</span>    <span class="token key atrule">config</span><span class="token punctuation">:</span>       <span class="token key atrule">default</span><span class="token punctuation">:</span> <span class="token comment" spellcheck="true"># 这里用default就是全局配置，如果是写服务名称，则是针对某个微服务的配置</span>        <span class="token key atrule">loggerLevel</span><span class="token punctuation">:</span> FULL <span class="token comment" spellcheck="true">#  日志级别 </span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>自定义Bean覆盖</strong></p><p>也可以基于Java代码来修改日志级别，先声明一个类，然后声明一个Logger.Level的对象：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">DefaultFeignConfiguration</span>  <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Bean</span>    <span class="token keyword">public</span> Logger<span class="token punctuation">.</span>Level <span class="token function">feignLogLevel</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>        <span class="token keyword">return</span> Logger<span class="token punctuation">.</span>Level<span class="token punctuation">.</span>BASIC<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 日志级别为BASIC</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>如果要<strong>全局生效</strong>，将其放到启动类的@EnableFeignClients这个注解中：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@EnableFeignClients</span><span class="token punctuation">(</span>defaultConfiguration <span class="token operator">=</span> DefaultFeignConfiguration<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span> <span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>如果是<strong>局部生效</strong>，则把它放到对应的@FeignClient这个注解中：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@FeignClient</span><span class="token punctuation">(</span>value <span class="token operator">=</span> <span class="token string">"userservice"</span><span class="token punctuation">,</span> configuration <span class="token operator">=</span> DefaultFeignConfiguration<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span> <span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><h3 id="❸优化"><a href="#❸优化" class="headerlink" title="❸优化"></a>❸优化</h3><p>Feign底层发起http请求，依赖于其它的框架。其底层客户端实现包括：</p><ul><li><p>URLConnection：默认实现，不支持连接池</p></li><li><p>Apache HttpClient ：支持连接池</p></li><li><p>OKHttp：支持连接池</p></li></ul><p>因此提高Feign的性能主要手段就是使用<strong>连接池</strong>代替默认的URLConnection。这里我们用Apache的HttpClient来演示。</p><p><strong>步骤一：</strong>引入依赖</p><p>在order-service的pom文件中引入Apache的HttpClient依赖：</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token comment" spellcheck="true">&lt;!--httpClient的依赖 --></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>io.github.openfeign<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>feign-httpclient<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤二：</strong>配置连接池</p><p>在order-service的application.yml中添加配置：</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">feign</span><span class="token punctuation">:</span>  <span class="token key atrule">client</span><span class="token punctuation">:</span>    <span class="token key atrule">config</span><span class="token punctuation">:</span>      <span class="token key atrule">default</span><span class="token punctuation">:</span> <span class="token comment" spellcheck="true"># default全局的配置</span>        <span class="token key atrule">loggerLevel</span><span class="token punctuation">:</span> BASIC <span class="token comment" spellcheck="true"># 日志级别，BASIC就是基本的请求和响应信息</span>  <span class="token key atrule">httpclient</span><span class="token punctuation">:</span>    <span class="token key atrule">enabled</span><span class="token punctuation">:</span> <span class="token boolean important">true </span><span class="token comment" spellcheck="true"># 开启feign对HttpClient的支持</span>    <span class="token key atrule">max-connections</span><span class="token punctuation">:</span> <span class="token number">200 </span><span class="token comment" spellcheck="true"># 最大的连接数</span>    <span class="token key atrule">max-connections-per-route</span><span class="token punctuation">:</span> <span class="token number">50 </span><span class="token comment" spellcheck="true"># 每个路径的最大连接数</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="❹最佳实践"><a href="#❹最佳实践" class="headerlink" title="❹最佳实践"></a>❹最佳实践</h3><p>所谓最近实践，就是使用过程中总结的经验，最好的一种使用方式。仔细可以发现，Feign的客户端与服务提供者的controller代码非常相似：</p><table><thead><tr><th>UserClient</th><th>UserController</th></tr></thead><tbody><tr><td><img src="https://img.jwt1399.top/img/202209241117530.png"></td><td><img src="https://img.jwt1399.top/img/202209241117602.png" style="zoom: 67%;" /></td></tr></tbody></table><p>有没有一种办法简化这种重复的代码编写呢？</p><p><strong>方案一：继承方式</strong></p><p>一样的代码可以通过继承来共享：</p><ul><li>1.定义一个API接口，利用定义方法，并基于SpringMVC注解做声明。</li><li>2.Feign客户端和Controller都集成该接口</li></ul><p><img src="https://img.jwt1399.top/img/202209241120933.png">优点：</p><ul><li>简单</li><li>实现了代码共享</li></ul><p>缺点：</p><ul><li><p>服务提供方、服务消费方紧耦合</p></li><li><p>参数列表中的注解映射并不会继承，因此Controller中必须再次声明方法、参数列表、注解</p></li></ul><p><strong>方案二：抽取方式</strong></p><p>将Feign的Client抽取为独立模块，并且把接口有关的POJO、默认的Feign配置都放到这个模块中，提供给所有消费者使用。</p><p>例如，将UserClient、User、Feign的默认配置都抽取到一个feign-api包中，所有微服务引用该依赖包，即可直接使用。</p><p><img src="https://img.jwt1399.top/img/202209241123914.png"></p><p><strong>步骤一：抽取</strong></p><p>首先创建一个module，命名为feign-api，在feign-api中然后引入feign的starter依赖</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework.cloud<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-cloud-starter-openfeign<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>然后，order-service中编写的UserClient、User、DefaultFeignConfiguration都复制到feign-api项目中</p><p><img src="https://img.jwt1399.top/img/202209241125595.png"></p><p><strong>步骤二：在order-service中使用feign-api</strong></p><p>首先，删除order-service中的UserClient、User、DefaultFeignConfiguration等类或接口。在order-service的pom文件中中引入feign-api的依赖：</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>cn.itcast.demo<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>feign-api<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>1.0<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>修改order-service中的所有与上述三个组件有关的导包部分，改成导入feign-api中的包</p><p><strong>步骤三：重启测试</strong></p><p>重启后，发现服务报错了：</p><p><img src="/../images/SpringCloud-%E5%AE%9E%E7%94%A8%E7%AF%87/image-20210714205623048.png"></p><p>这是因为UserClient现在在cn.itcast.feign.clients包下，而order-service的@EnableFeignClients注解是在cn.itcast.order包下，不在同一个包，无法扫描到UserClient。</p><p><strong>步骤四：解决扫描包问题</strong></p><p>方式一：指定Feign应该扫描的包：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@EnableFeignClients</span><span class="token punctuation">(</span>basePackages <span class="token operator">=</span> <span class="token string">"cn.itcast.feign.clients"</span><span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>方式二：指定需要加载的Client接口：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@EnableFeignClients</span><span class="token punctuation">(</span>clients <span class="token operator">=</span> <span class="token punctuation">{</span>UserClient<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><h1 id="6-统一配置管理"><a href="#6-统一配置管理" class="headerlink" title="6.统一配置管理"></a>6.统一配置管理</h1><h2 id="①Nacos"><a href="#①Nacos" class="headerlink" title="①Nacos"></a>①Nacos</h2><p>Nacos除了可以做注册中心，同样可以做配置管理来使用。当微服务部署的实例越来越多，达到数十、数百时，逐个修改微服务配置就会让人抓狂，而且很容易出错。我们需要一种统一配置管理方案，可以集中管理所有实例的配置。</p><p><img src="https://img.jwt1399.top/img/202209241704284.png"></p><p>Nacos一方面可以将配置集中管理，另一方可以在配置变更时，及时通知微服务，实现配置的热更新。</p><h3 id="❶统一配置管理"><a href="#❶统一配置管理" class="headerlink" title="❶统一配置管理"></a>❶统一配置管理</h3><p><strong>步骤一：在nacos中添加配置文件</strong></p><table><thead><tr><th>1.在Nacos中添加配置信息</th><th>2.在弹出表单中填写配置信息</th></tr></thead><tbody><tr><td><img src="https://img.jwt1399.top/img/202209241700314.png"></td><td><img src="https://img.jwt1399.top/img/202209241700595.png"></td></tr></tbody></table><p><strong>步骤二：从微服务拉取配置</strong></p><p>微服务要拉取nacos中管理的配置，并且与本地的application.yml配置合并，才能完成项目启动。</p><p>但如果尚未读取application.yml，又如何得知nacos地址呢？</p><p>因此spring引入了一种新的配置文件：<code>bootstrap.yaml</code>文件，会在application.yml之前被读取，流程如下：</p><table><thead><tr><th>传统读取配置</th><th><img src="https://img.jwt1399.top/img/202209241707016.png"></th></tr></thead><tbody><tr><td><strong>nacos管理配置</strong></td><td><img src="https://img.jwt1399.top/img/202209241707835.png"></td></tr></tbody></table><p>1.引入Nacos的配置管理客户端依赖</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token comment" spellcheck="true">&lt;!--nacos配置管理依赖--></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>com.alibaba.cloud<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-cloud-starter-alibaba-nacos-config<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>2.在userservice的resource目录添加一个bootstrap.yml文件，这个文件是引导文件，优先级高于application.yml：</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">spring</span><span class="token punctuation">:</span>  <span class="token key atrule">application</span><span class="token punctuation">:</span>    <span class="token key atrule">name</span><span class="token punctuation">:</span> userservice <span class="token comment" spellcheck="true"># 服务名称</span>  <span class="token key atrule">profiles</span><span class="token punctuation">:</span>    <span class="token key atrule">active</span><span class="token punctuation">:</span> dev <span class="token comment" spellcheck="true">#开发环境，这里是dev </span>  <span class="token key atrule">cloud</span><span class="token punctuation">:</span>    <span class="token key atrule">nacos</span><span class="token punctuation">:</span>      <span class="token key atrule">server-addr</span><span class="token punctuation">:</span> localhost<span class="token punctuation">:</span><span class="token number">8848 </span><span class="token comment" spellcheck="true"># Nacos地址</span>      <span class="token key atrule">config</span><span class="token punctuation">:</span>        <span class="token key atrule">file-extension</span><span class="token punctuation">:</span> yaml <span class="token comment" spellcheck="true"># 文件后缀名</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>这里会根据spring.cloud.nacos.server-addr获取nacos地址，再根据</p><p><code>$&#123;spring.application.name&#125;-$&#123;spring.profiles.active&#125;.$&#123;spring.cloud.nacos.config.file-extension&#125;</code>作为文件id，来读取配置。本例中，就是去读取<code>userservice-dev.yaml</code></p><p><img src="https://img.jwt1399.top/img/202209241709238.png"></p><p>3.在user-service中的UserController中添加业务逻辑，读取pattern.dateformat配置</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Slf4j</span><span class="token annotation punctuation">@RestController</span><span class="token annotation punctuation">@RequestMapping</span><span class="token punctuation">(</span><span class="token string">"/user"</span><span class="token punctuation">)</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">UserController</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Value</span><span class="token punctuation">(</span><span class="token string">"${pattern.dateformat}"</span><span class="token punctuation">)</span>    <span class="token keyword">private</span> String dateformat<span class="token punctuation">;</span>    <span class="token annotation punctuation">@GetMapping</span><span class="token punctuation">(</span><span class="token string">"now"</span><span class="token punctuation">)</span>    <span class="token keyword">public</span> String <span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>        <span class="token keyword">return</span> LocalDateTime<span class="token punctuation">.</span><span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">format</span><span class="token punctuation">(</span>DateTimeFormatter<span class="token punctuation">.</span><span class="token function">ofPattern</span><span class="token punctuation">(</span>dateformat<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>访问页面<code>http://127.0.0.1:8081/user/now</code>即可看到时间</p><p><strong>总结：</strong>将配置交给Nacos管理的步骤</p><ul><li><p>①在Nacos中添加配置文件</p></li><li><p>②在微服务中引入nacos的config依赖</p></li><li><p>③在微服务中添加bootstrap.yml，配置nacos地址、当前环境、服务名称、文件后缀名。</p></li></ul><h3 id="❷配置热更新"><a href="#❷配置热更新" class="headerlink" title="❷配置热更新"></a>❷配置热更新</h3><p>我们最终的目的，是修改nacos中的配置后，微服务无需重启即可让配置生效，也就是<strong>配置热更新</strong>。</p><p>要实现配置热更新，可以使用两种方式：</p><p><strong>方式一：通过 Spring Cloud 原生注解 <code>@RefreshScope</code> 实现配置自动更新</strong></p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@RefreshScope</span> <span class="token comment" spellcheck="true">//添加注解</span><span class="token annotation punctuation">@Slf4j</span><span class="token annotation punctuation">@RestController</span><span class="token annotation punctuation">@RequestMapping</span><span class="token punctuation">(</span><span class="token string">"/user"</span><span class="token punctuation">)</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">UserController</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Value</span><span class="token punctuation">(</span><span class="token string">"${pattern.dateformat}"</span><span class="token punctuation">)</span>    <span class="token keyword">private</span> String dateformat<span class="token punctuation">;</span>    <span class="token annotation punctuation">@GetMapping</span><span class="token punctuation">(</span><span class="token string">"now"</span><span class="token punctuation">)</span>    <span class="token keyword">public</span> String <span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>        <span class="token keyword">return</span> LocalDateTime<span class="token punctuation">.</span><span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">format</span><span class="token punctuation">(</span>DateTimeFormatter<span class="token punctuation">.</span><span class="token function">ofPattern</span><span class="token punctuation">(</span>dateformat<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>方式二：使用@ConfigurationProperties注解代替@Value注解</strong></p><p>在user-service服务中，添加一个类，读取patterrn.dateformat属性：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Component</span><span class="token annotation punctuation">@Data</span><span class="token annotation punctuation">@ConfigurationProperties</span><span class="token punctuation">(</span>prefix <span class="token operator">=</span> <span class="token string">"pattern"</span><span class="token punctuation">)</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">PatternProperties</span> <span class="token punctuation">{</span>    <span class="token keyword">private</span> String dateformat<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>在UserController中使用这个类代替@Value：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Slf4j</span><span class="token annotation punctuation">@RestController</span><span class="token annotation punctuation">@RequestMapping</span><span class="token punctuation">(</span><span class="token string">"/user"</span><span class="token punctuation">)</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">UserController</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Autowired</span>    <span class="token keyword">private</span> PatternProperties patternProperties<span class="token punctuation">;</span>    <span class="token annotation punctuation">@GetMapping</span><span class="token punctuation">(</span><span class="token string">"now"</span><span class="token punctuation">)</span>    <span class="token keyword">public</span> String <span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>        <span class="token keyword">return</span> LocalDateTime<span class="token punctuation">.</span><span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">format</span><span class="token punctuation">(</span>DateTimeFormatter<span class="token punctuation">.</span><span class="token function">ofPattern</span><span class="token punctuation">(</span>patternProperties<span class="token punctuation">.</span><span class="token function">getDateformat</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="❸配置共享"><a href="#❸配置共享" class="headerlink" title="❸配置共享"></a>❸配置共享</h3><p>其实微服务启动时，会去nacos读取多个配置文件，例如：</p><ul><li><p><code>[spring.application.name]-[spring.profiles.active].yaml</code>，例如：userservice-dev.yaml</p></li><li><p><code>[spring.application.name].yaml</code>，例如：userservice.yaml</p></li></ul><p>而<code>[spring.application.name].yaml</code>不包含环境，因此可以被多个环境共享。</p><p>例如：在nacos配置3个文件：userservice-dev.yaml、userservice-test.yaml、userservice.yaml</p><p>UserApplication(8081)使用的profile是dev，UserApplication2(8082)使用的profile是test，运行后不管是dev，还是test环境，都读取到了userservice.yaml中属性的值。</p><p>但是当nacos和服务本地同时出现相同属性时，优先级有高低之分：userservice-dev.yaml &gt; userservice.yaml &gt; application.yml</p><p><img src="https://img.jwt1399.top/img/202209241736075.png"></p><p><strong>多服务共享配置</strong></p><p>不同微服务之间可以共享配置文件，通过下面的两种方式来指定</p><p>方式一：</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">spring</span><span class="token punctuation">:</span>  <span class="token key atrule">application</span><span class="token punctuation">:</span>    <span class="token key atrule">name</span><span class="token punctuation">:</span> userservice <span class="token comment" spellcheck="true"># 服务名称</span>  <span class="token key atrule">profiles</span><span class="token punctuation">:</span>    <span class="token key atrule">active</span><span class="token punctuation">:</span> dev <span class="token comment" spellcheck="true">#开发环境，这里是dev</span>  <span class="token key atrule">cloud</span><span class="token punctuation">:</span>    <span class="token key atrule">nacos</span><span class="token punctuation">:</span>      <span class="token key atrule">server-addr</span><span class="token punctuation">:</span> localhost<span class="token punctuation">:</span><span class="token number">8848 </span><span class="token comment" spellcheck="true"># Nacos地址</span>      <span class="token key atrule">config</span><span class="token punctuation">:</span>        <span class="token key atrule">file-extension</span><span class="token punctuation">:</span> yaml <span class="token comment" spellcheck="true"># 文件后缀名</span>        <span class="token key atrule">shared-configs</span><span class="token punctuation">:</span> <span class="token comment" spellcheck="true"># 多微服务间共享的配置列表</span>         <span class="token punctuation">-</span> <span class="token key atrule">dataId</span><span class="token punctuation">:</span> common.yaml <span class="token comment" spellcheck="true"># 要共享的配置文件id</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>方式二：</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">spring</span><span class="token punctuation">:</span>  <span class="token key atrule">application</span><span class="token punctuation">:</span>    <span class="token key atrule">name</span><span class="token punctuation">:</span> userservice <span class="token comment" spellcheck="true"># 服务名称</span>  <span class="token key atrule">profiles</span><span class="token punctuation">:</span>    <span class="token key atrule">active</span><span class="token punctuation">:</span> dev <span class="token comment" spellcheck="true">#开发环境，这里是dev</span>  <span class="token key atrule">cloud</span><span class="token punctuation">:</span>    <span class="token key atrule">nacos</span><span class="token punctuation">:</span>      <span class="token key atrule">server-addr</span><span class="token punctuation">:</span> localhost<span class="token punctuation">:</span><span class="token number">8848 </span><span class="token comment" spellcheck="true"># Nacos地址</span>      <span class="token key atrule">config</span><span class="token punctuation">:</span>        <span class="token key atrule">file-extension</span><span class="token punctuation">:</span> yaml <span class="token comment" spellcheck="true"># 文件后缀名</span>        <span class="token key atrule">extends-configs</span><span class="token punctuation">:</span> <span class="token comment" spellcheck="true"># 多微服务间共享的配置列表</span>         <span class="token punctuation">-</span> <span class="token key atrule">dataId</span><span class="token punctuation">:</span> extend.yaml <span class="token comment" spellcheck="true"># 要共享的配置文件id</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>多种配置的优先级：</p><p><img src="https://img.jwt1399.top/img/202209241748244.png"></p><h3 id="❹搭建Nacos集群"><a href="#❹搭建Nacos集群" class="headerlink" title="❹搭建Nacos集群"></a>❹搭建Nacos集群</h3><p><img src="https://img.jwt1399.top/img/202209242331548.png"></p><table><thead><tr><th>节点</th><th>ip</th><th>port</th></tr></thead><tbody><tr><td>nacos1</td><td>192.168.150.1</td><td>8845</td></tr><tr><td>nacos2</td><td>192.168.150.1</td><td>8846</td></tr><tr><td>nacos3</td><td>192.168.150.1</td><td>8847</td></tr></tbody></table><h4 id="⓵初始化数据库"><a href="#⓵初始化数据库" class="headerlink" title="⓵初始化数据库"></a>⓵初始化数据库</h4><p>Nacos默认数据存储在内嵌数据库Derby中，不属于生产可用的数据库。官方推荐的最佳实践是使用带有主从的高可用数据库集群。这里我们以单点的数据库为例。</p><p>首先新建一个数据库，命名为nacos，而后导入官方提供的SQL：<a href="https://github.com/alibaba/nacos/blob/master/distribution/conf/schema.sql">nacos&#x2F;schema.sql </a></p><h4 id="⓶部署配置nacos"><a href="#⓶部署配置nacos" class="headerlink" title="⓶部署配置nacos"></a>⓶部署配置nacos</h4><p>下载nacos后，进入nacos的conf目录，修改配置文件cluster.conf.example，重命名为cluster.conf</p><p>然后添加内容：</p><pre><code>127.0.0.1:8845127.0.0.1.8846127.0.0.1.8847</code></pre><p>然后修改application.properties文件，添加数据库配置</p><pre class="line-numbers language-properties"><code class="language-properties"><span class="token attr-name">spring.datasource.platform</span><span class="token punctuation">=</span><span class="token attr-value">mysql</span><span class="token attr-name">db.num</span><span class="token punctuation">=</span><span class="token attr-value">1</span><span class="token attr-name">db.url.0</span><span class="token punctuation">=</span><span class="token attr-value">jdbc:mysql://127.0.0.1:3306/nacos?characterEncoding=utf8&amp;connectTimeout=1000&amp;socketTimeout=3000&amp;autoReconnect=true&amp;useUnicode=true&amp;useSSL=false&amp;serverTimezone=UTC</span><span class="token attr-name">db.user.0</span><span class="token punctuation">=</span><span class="token attr-value">root</span><span class="token attr-name">db.password.0</span><span class="token punctuation">=</span><span class="token attr-value">root</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="⓷启动nacos集群"><a href="#⓷启动nacos集群" class="headerlink" title="⓷启动nacos集群"></a>⓷启动nacos集群</h4><p>将nacos文件夹复制三份，分别命名为：nacos1、nacos2、nacos3；然后分别修改三个文件夹中的application.properties</p><p>nacos1:</p><pre class="line-numbers language-properties"><code class="language-properties"><span class="token attr-name">server.port</span><span class="token punctuation">=</span><span class="token attr-value">8845</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>nacos2:</p><pre class="line-numbers language-properties"><code class="language-properties"><span class="token attr-name">server.port</span><span class="token punctuation">=</span><span class="token attr-value">8846</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>nacos3:</p><pre class="line-numbers language-properties"><code class="language-properties"><span class="token attr-name">server.port</span><span class="token punctuation">=</span><span class="token attr-value">8847</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>然后分别启动三个nacos节点：<code>startup.cmd</code></p><h4 id="⓸nginx反向代理"><a href="#⓸nginx反向代理" class="headerlink" title="⓸nginx反向代理"></a>⓸nginx反向代理</h4><p>下载nginx后，修改conf&#x2F;nginx.conf文件，配置如下：</p><pre class="line-numbers language-json"><code class="language-json">upstream nacos-cluster <span class="token punctuation">{</span>  server <span class="token number">127.0</span>.<span class="token number">0.1</span><span class="token operator">:</span><span class="token number">8845</span><span class="token punctuation">;</span>    server <span class="token number">127.0</span>.<span class="token number">0.1</span><span class="token operator">:</span><span class="token number">8846</span><span class="token punctuation">;</span>    server <span class="token number">127.0</span>.<span class="token number">0.1</span><span class="token operator">:</span><span class="token number">8847</span><span class="token punctuation">;</span><span class="token punctuation">}</span>server <span class="token punctuation">{</span>    listen       <span class="token number">80</span><span class="token punctuation">;</span>    server_name  localhost<span class="token punctuation">;</span>    location /nacos <span class="token punctuation">{</span>        proxy_pass http<span class="token operator">:</span>//nacos-cluster<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>而后在浏览器访问：<code>http://localhost/nacos</code>即可。</p><p>代码中application.yml文件配置如下：</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">spring</span><span class="token punctuation">:</span>  <span class="token key atrule">cloud</span><span class="token punctuation">:</span>    <span class="token key atrule">nacos</span><span class="token punctuation">:</span>      <span class="token key atrule">server-addr</span><span class="token punctuation">:</span> localhost<span class="token punctuation">:</span><span class="token number">80 </span><span class="token comment" spellcheck="true"># Nacos地址</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><h4 id="⓹优化"><a href="#⓹优化" class="headerlink" title="⓹优化"></a>⓹优化</h4><ul><li><p>实际部署时，需要给做反向代理的nginx服务器设置一个域名，这样后续如果有服务器迁移，nacos的客户端也无需更改配置</p></li><li><p>Nacos的各个节点应该部署到多个不同服务器，做好容灾和隔离</p></li></ul><h1 id="7-统一网关路由"><a href="#7-统一网关路由" class="headerlink" title="7.统一网关路由"></a>7.统一网关路由</h1><h2 id="①简介"><a href="#①简介" class="headerlink" title="①简介"></a>①简介</h2><p>Spring Cloud Gateway 是 Spring Cloud 的一个全新项目，该项目是基于 Spring 5.0，Spring Boot 2.0 和 Project Reactor 等响应式编程和事件流技术开发的网关，它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。</p><p>Gateway网关是我们服务的守门神，所有微服务的统一入口。网关的<strong>核心功能特性</strong>：</p><ul><li>路由和负载均衡<ul><li>一切请求都必须先经过gateway，但网关不处理业务，而是根据某种规则，把请求转发到某个微服务，这个过程叫做路由。当然路由的目标服务有多个时，还需要做负载均衡。</li></ul></li><li>权限控制<ul><li>网关作为微服务入口，需要校验用户是是否有请求资格，如果没有则进行拦截。</li></ul></li><li>限流<ul><li>当请求流量过高时，在网关中按照下流的微服务能够接受的速度来放行请求，避免服务压力过大。</li></ul></li></ul><img src="../images/SpringCloud-实用篇/image-20220925101634632.png" alt="架构图" style="zoom:50%;" /><p>在SpringCloud中网关的实现包括两种：</p><ul><li>gateway</li><li>zuul</li></ul><p>Zuul是基于Servlet的实现，属于阻塞式编程。而SpringCloudGateway则是基于Spring5中提供的WebFlux，属于响应式编程的实现，具备更好的性能。</p><h2 id="②快速入门"><a href="#②快速入门" class="headerlink" title="②快速入门"></a>②快速入门</h2><p>下面，我们就演示下网关的基本路由功能。基本步骤如下：</p><ol><li>创建新模块，引入网关依赖</li><li>编写启动类</li><li>编写基础配置和路由规则</li><li>启动网关服务进行测试</li></ol><h3 id="❶创建新模块，引入网关依赖"><a href="#❶创建新模块，引入网关依赖" class="headerlink" title="❶创建新模块，引入网关依赖"></a>❶创建新模块，引入网关依赖</h3><p>创建服务：</p><p><img src="https://img.jwt1399.top/img/202209251033539.png"></p><p>引入依赖：</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token comment" spellcheck="true">&lt;!--网关--></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework.cloud<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-cloud-starter-gateway<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span class="token comment" spellcheck="true">&lt;!--nacos服务发现依赖--></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>com.alibaba.cloud<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-cloud-starter-alibaba-nacos-discovery<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="❷编写启动类"><a href="#❷编写启动类" class="headerlink" title="❷编写启动类"></a>❷编写启动类</h3><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@SpringBootApplication</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">GatewayApplication</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>        SpringApplication<span class="token punctuation">.</span><span class="token function">run</span><span class="token punctuation">(</span>GatewayApplication<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">,</span> args<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="❸编写基础配置和路由规则"><a href="#❸编写基础配置和路由规则" class="headerlink" title="❸编写基础配置和路由规则"></a>❸编写基础配置和路由规则</h3><p>创建application.yml文件，内容如下：</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">server</span><span class="token punctuation">:</span>  <span class="token key atrule">port</span><span class="token punctuation">:</span> <span class="token number">10010 </span><span class="token comment" spellcheck="true"># 网关端口</span><span class="token key atrule">spring</span><span class="token punctuation">:</span>  <span class="token key atrule">application</span><span class="token punctuation">:</span>    <span class="token key atrule">name</span><span class="token punctuation">:</span> gateway <span class="token comment" spellcheck="true"># 服务名称</span>  <span class="token key atrule">cloud</span><span class="token punctuation">:</span>    <span class="token key atrule">nacos</span><span class="token punctuation">:</span>      <span class="token key atrule">server-addr</span><span class="token punctuation">:</span> localhost<span class="token punctuation">:</span><span class="token number">8848 </span><span class="token comment" spellcheck="true"># nacos地址</span>    <span class="token key atrule">gateway</span><span class="token punctuation">:</span>      <span class="token key atrule">routes</span><span class="token punctuation">:</span> <span class="token comment" spellcheck="true"># 网关路由配置</span>        <span class="token punctuation">-</span> <span class="token key atrule">id</span><span class="token punctuation">:</span> user<span class="token punctuation">-</span>service <span class="token comment" spellcheck="true"># 路由id，自定义，只要唯一即可</span>          <span class="token comment" spellcheck="true"># uri: http://127.0.0.1:8081 # 路由的目标地址 http就是固定地址</span>          <span class="token key atrule">uri</span><span class="token punctuation">:</span> lb<span class="token punctuation">:</span>//userservice <span class="token comment" spellcheck="true"># 路由的目标地址 lb就是负载均衡，后面跟服务名称</span>          <span class="token key atrule">predicates</span><span class="token punctuation">:</span> <span class="token comment" spellcheck="true"># 路由断言，也就是判断请求是否符合路由规则的条件</span>            <span class="token punctuation">-</span> Path=/user/** <span class="token comment" spellcheck="true"># 这个是按照路径匹配，只要以/user/开头就符合要求</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>我们将符合<code>Path</code> 规则的一切请求，都代理到 <code>uri</code>参数指定的地址。</p><p>本例中，我们将 <code>/user/**</code>开头的请求，代理到<code>lb://userservice</code>，lb是负载均衡，根据服务名拉取服务列表，实现负载均衡。</p><h3 id="❹启动网关服务进行测试"><a href="#❹启动网关服务进行测试" class="headerlink" title="❹启动网关服务进行测试"></a>❹启动网关服务进行测试</h3><p>启动网关，访问<code>http://localhost:10010/user/1</code>时，符合<code>/user/**</code>规则，请求转发到uri：<code>http://userservice/user/1</code>，得到了结果：</p><p><img src="https://img.jwt1399.top/img/202209251054663.png"></p><p>整个访问的流程如下：</p><p><img src="https://img.jwt1399.top/img/202209251055650.png"></p><h3 id="❺总结-1"><a href="#❺总结-1" class="headerlink" title="❺总结"></a>❺总结</h3><p>网关搭建步骤：</p><ol><li><p>创建项目，引入nacos服务发现和gateway依赖</p></li><li><p>配置application.yml，包括服务基本信息、nacos地址、路由</p></li></ol><p>路由配置包括：</p><ol><li><p>路由id：路由的唯一标示</p></li><li><p>路由目标（uri）：路由的目标地址，http代表固定地址，lb代表根据服务名负载均衡</p></li><li><p>路由断言（predicates）：判断路由的规则</p></li><li><p>路由过滤器（filters）：对请求或响应做处理</p></li></ol><h2 id="③断言工厂"><a href="#③断言工厂" class="headerlink" title="③断言工厂"></a>③断言工厂</h2><p>我们在配置文件中写的断言规则只是字符串，这些字符串会被Predicate Factory读取并处理，转变为路由判断的条件</p><p>例如<code>Path=/user/**</code>是按照路径匹配，这个规则是由</p><p><code>org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory</code>类来</p><p>处理的，像这样的断言工厂在SpringCloudGateway还有十几个:</p><table><thead><tr><th><strong>名称</strong></th><th><strong>说明</strong></th><th><strong>示例</strong></th></tr></thead><tbody><tr><td>After</td><td>是某个时间点后的请求</td><td>-  After&#x3D;2037-01-20T17:42:47.789-07:00[America&#x2F;Denver]</td></tr><tr><td>Before</td><td>是某个时间点之前的请求</td><td>-  Before&#x3D;2031-04-13T15:14:47.433+08:00[Asia&#x2F;Shanghai]</td></tr><tr><td>Between</td><td>是某两个时间点之前的请求</td><td>-  Between&#x3D;2037-01-20T17:42:47.789-07:00[America&#x2F;Denver],  2037-01-21T17:42:47.789-07:00[America&#x2F;Denver]</td></tr><tr><td>Cookie</td><td>请求必须包含某些cookie</td><td>- Cookie&#x3D;chocolate, ch.p</td></tr><tr><td>Header</td><td>请求必须包含某些header</td><td>- Header&#x3D;X-Request-Id, \d+</td></tr><tr><td>Host</td><td>请求必须是访问某个host（域名）</td><td>-  Host&#x3D;<strong>.somehost.org,</strong>.anotherhost.org</td></tr><tr><td>Method</td><td>请求方式必须是指定方式</td><td>- Method&#x3D;GET,POST</td></tr><tr><td>Path</td><td>请求路径必须符合指定规则</td><td>- Path&#x3D;&#x2F;red&#x2F;{segment},&#x2F;blue&#x2F;**</td></tr><tr><td>Query</td><td>请求参数必须包含指定参数</td><td>- Query&#x3D;name, Jack或者-  Query&#x3D;name</td></tr><tr><td>RemoteAddr</td><td>请求者的ip必须是指定范围</td><td>- RemoteAddr&#x3D;192.168.1.1&#x2F;24</td></tr><tr><td>Weight</td><td>权重处理</td><td></td></tr></tbody></table><p>我们只需要掌握Path这种路由工程就可以了。</p><h2 id="④过滤工厂"><a href="#④过滤工厂" class="headerlink" title="④过滤工厂"></a>④过滤工厂</h2><p>GatewayFilter是网关中提供的一种过滤器，可以对进入网关的请求和微服务返回的响应做处理：</p><p><img src="https://img.jwt1399.top/img/202209251101960.png"></p><h3 id="❶路由过滤器的种类"><a href="#❶路由过滤器的种类" class="headerlink" title="❶路由过滤器的种类"></a>❶路由过滤器的种类</h3><p>Spring提供了31种不同的路由过滤器工厂。例如：</p><table><thead><tr><th><strong>名称</strong></th><th><strong>说明</strong></th></tr></thead><tbody><tr><td>AddRequestHeader</td><td>给当前请求添加一个请求头</td></tr><tr><td>RemoveRequestHeader</td><td>移除请求中的一个请求头</td></tr><tr><td>AddResponseHeader</td><td>给响应结果中添加一个响应头</td></tr><tr><td>RemoveResponseHeader</td><td>从响应结果中移除有一个响应头</td></tr><tr><td>RequestRateLimiter</td><td>限制请求的流量</td></tr><tr><td>……</td><td>……</td></tr></tbody></table><h3 id="❷响应头过滤器"><a href="#❷响应头过滤器" class="headerlink" title="❷响应头过滤器"></a>❷响应头过滤器</h3><p>下面我们以AddResponseHeader 为例来讲解。</p><blockquote><p><strong>需求</strong>：给所有进入userservice的请求添加一个请求头：Truth&#x3D;This is a test !</p></blockquote><p>只需要修改gateway服务的application.yml文件，添加路由过滤即可：</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">spring</span><span class="token punctuation">:</span>  <span class="token key atrule">cloud</span><span class="token punctuation">:</span>    <span class="token key atrule">gateway</span><span class="token punctuation">:</span>      <span class="token key atrule">routes</span><span class="token punctuation">:</span>      <span class="token punctuation">-</span> <span class="token key atrule">id</span><span class="token punctuation">:</span> user<span class="token punctuation">-</span>service         <span class="token key atrule">uri</span><span class="token punctuation">:</span> lb<span class="token punctuation">:</span>//userservice         <span class="token key atrule">predicates</span><span class="token punctuation">:</span>         <span class="token punctuation">-</span> Path=/user/**       <span class="token punctuation">-</span> <span class="token key atrule">id</span><span class="token punctuation">:</span> order<span class="token punctuation">-</span>service        <span class="token key atrule">uri</span><span class="token punctuation">:</span> lb<span class="token punctuation">:</span>//orderservice        <span class="token key atrule">predicates</span><span class="token punctuation">:</span>        <span class="token punctuation">-</span> Path=/order/**        <span class="token key atrule">filters</span><span class="token punctuation">:</span> <span class="token comment" spellcheck="true"># 过滤器</span>        <span class="token punctuation">-</span> AddResponseHeader=Truth<span class="token punctuation">,</span> This is a test! <span class="token comment" spellcheck="true"># 添加请求头</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>当前过滤器写在userservice路由下，因此仅仅对访问userservice的请求有效。</p><p><img src="https://img.jwt1399.top/img/202209251112506.png"></p><h3 id="❸默认过滤器"><a href="#❸默认过滤器" class="headerlink" title="❸默认过滤器"></a>❸默认过滤器</h3><p>如果要对所有的路由都生效，则可以将过滤器工厂写到default下。格式如下：</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">spring</span><span class="token punctuation">:</span>  <span class="token key atrule">cloud</span><span class="token punctuation">:</span>    <span class="token key atrule">gateway</span><span class="token punctuation">:</span>      <span class="token key atrule">routes</span><span class="token punctuation">:</span>      <span class="token punctuation">-</span> <span class="token key atrule">id</span><span class="token punctuation">:</span> user<span class="token punctuation">-</span>service         <span class="token key atrule">uri</span><span class="token punctuation">:</span> lb<span class="token punctuation">:</span>//userservice         <span class="token key atrule">predicates</span><span class="token punctuation">:</span>         <span class="token punctuation">-</span> Path=/user/**      <span class="token key atrule">default-filters</span><span class="token punctuation">:</span> <span class="token comment" spellcheck="true"># 默认过滤项</span>      <span class="token punctuation">-</span> AddResponseHeader=Truth<span class="token punctuation">,</span> This is a test! <span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="❹总结-1"><a href="#❹总结-1" class="headerlink" title="❹总结"></a>❹总结</h3><p>过滤器的作用是什么？</p><ul><li><p>① 对路由的请求或响应做加工处理，比如添加请求头</p></li><li><p>② 配置在路由下的过滤器只对当前路由的请求生效</p></li></ul><p>defaultFilters的作用是什么？</p><ul><li>① 对所有路由都生效的过滤器</li></ul><h2 id="⑤全局过滤器"><a href="#⑤全局过滤器" class="headerlink" title="⑤全局过滤器"></a>⑤全局过滤器</h2><p>上一节学习的过滤器，网关提供了31种，但每一种过滤器的作用都是固定的。如果我们希望拦截请求，做自己的业务逻辑则没办法实现。</p><h3 id="❶全局过滤器作用"><a href="#❶全局过滤器作用" class="headerlink" title="❶全局过滤器作用"></a>❶全局过滤器作用</h3><p>全局过滤器的作用也是处理一切进入网关的请求和微服务响应，与GatewayFilter的作用一样。区别在于GatewayFilter通过配置定义，处理逻辑是固定的；而GlobalFilter的逻辑需要自己写代码实现。</p><p>定义方式是实现GlobalFilter接口。</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">GlobalFilter</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">/**     *  处理当前请求，有必要的话通过{@link GatewayFilterChain}将请求交给下一个过滤器处理     *     * @param exchange 请求上下文，里面可以获取Request、Response等信息     * @param chain 用来把请求委托给下一个过滤器      * @return {@code Mono&lt;Void>} 返回标示当前过滤器业务结束     */</span>    Mono<span class="token operator">&lt;</span>Void<span class="token operator">></span> <span class="token function">filter</span><span class="token punctuation">(</span>ServerWebExchange exchange<span class="token punctuation">,</span> GatewayFilterChain chain<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>在filter中编写自定义逻辑，可以实现下列功能：</p><ul><li>登录状态判断</li><li>权限校验</li><li>请求限流等</li></ul><h3 id="❷自定义全局过滤器"><a href="#❷自定义全局过滤器" class="headerlink" title="❷自定义全局过滤器"></a>❷自定义全局过滤器</h3><p>需求：定义全局过滤器，拦截请求，判断请求的参数是否满足下面条件：</p><ul><li><p>参数中是否有authorization，</p></li><li><p>authorization参数值是否为admin</p></li><li><p>如果同时满足则放行，否则拦截</p></li></ul><p>实现：在gateway中定义一个过滤器：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">package</span> cn<span class="token punctuation">.</span>itcast<span class="token punctuation">.</span>gateway<span class="token punctuation">.</span>filters<span class="token punctuation">;</span><span class="token annotation punctuation">@Order</span><span class="token punctuation">(</span><span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token annotation punctuation">@Component</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">AuthorizeFilter</span> <span class="token keyword">implements</span> <span class="token class-name">GlobalFilter</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> Mono<span class="token operator">&lt;</span>Void<span class="token operator">></span> <span class="token function">filter</span><span class="token punctuation">(</span>ServerWebExchange exchange<span class="token punctuation">,</span> GatewayFilterChain chain<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 1.获取请求参数</span>        MultiValueMap<span class="token operator">&lt;</span>String<span class="token punctuation">,</span> String<span class="token operator">></span> params <span class="token operator">=</span> exchange<span class="token punctuation">.</span><span class="token function">getRequest</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getQueryParams</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 2.获取authorization参数</span>        String auth <span class="token operator">=</span> params<span class="token punctuation">.</span><span class="token function">getFirst</span><span class="token punctuation">(</span><span class="token string">"authorization"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 3.校验</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token string">"admin"</span><span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span>auth<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 放行</span>            <span class="token keyword">return</span> chain<span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span>exchange<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 4.拦截</span>        <span class="token comment" spellcheck="true">// 4.1.禁止访问，设置状态码</span>        exchange<span class="token punctuation">.</span><span class="token function">getResponse</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">setStatusCode</span><span class="token punctuation">(</span>HttpStatus<span class="token punctuation">.</span>FORBIDDEN<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 4.2.结束处理</span>        <span class="token keyword">return</span> exchange<span class="token punctuation">.</span><span class="token function">getResponse</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">setComplete</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="❸过滤器执行顺序"><a href="#❸过滤器执行顺序" class="headerlink" title="❸过滤器执行顺序"></a>❸过滤器执行顺序</h3><p>请求进入网关会碰到三类过滤器：DefaultFilter、当前路由的过滤器、GlobalFilter</p><p>请求路由后，会将三个过滤器合并到一个过滤器链（集合）中，排序后依次执行每个过滤器：</p><p><img src="https://img.jwt1399.top/img/202209251133009.png"></p><p>排序的规则是什么呢？</p><ul><li>每一个过滤器都必须指定一个int类型的order值，<strong>order值越小，优先级越高，执行顺序越靠前</strong>。</li><li>GlobalFilter通过实现Ordered接口，或者添加@Order注解来指定order值，由我们自己指定</li><li>路由过滤器和defaultFilter的order由Spring指定，默认是按照声明顺序从1递增。</li><li>当过滤器的order值一样时，会按照 defaultFilter &gt; 路由过滤器 &gt; GlobalFilter的顺序执行。</li></ul><p>详细内容，可以查看源码：</p><p><code>org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#getFilters()</code>方法是先加载defaultFilters，然后再加载某个route的filters，然后合并。</p><p><code>org.springframework.cloud.gateway.handler.FilteringWebHandler#handle()</code>方法会加载全局过滤器，与前面的过滤器合并后根据order排序，组织过滤器链</p><h3 id="❹总结-2"><a href="#❹总结-2" class="headerlink" title="❹总结"></a>❹总结</h3><p>全局过滤器的作用是什么？</p><ul><li>对所有路由都生效的过滤器，并且可以自定义处理逻辑</li></ul><p>实现全局过滤器的步骤？</p><ul><li><p>①实现GlobalFilter接口</p></li><li><p>②添加@Order注解或实现Ordered接口</p></li><li><p>③编写处理逻辑</p></li></ul><p>路由过滤器、defaultFilter、全局过滤器的执行顺序？</p><ul><li><p>①order值越小，优先级越高</p></li><li><p>②当order值一样时，顺序是defaultFilter &gt; 局部的路由过滤器 &gt; 全局过滤器</p></li></ul><h2 id="⑥跨域问题"><a href="#⑥跨域问题" class="headerlink" title="⑥跨域问题"></a>⑥跨域问题</h2><h3 id="❶什么是跨域问题"><a href="#❶什么是跨域问题" class="headerlink" title="❶什么是跨域问题"></a>❶什么是跨域问题</h3><p>跨域：域名不一致就是跨域，主要包括：</p><ul><li><p>域名不同： <code>www.taobao.com</code> 和 <code>www.taobao.org</code> </p></li><li><p>域名相同，端口不同：<code>localhost:8080</code> 和 <code>localhost8081</code></p></li></ul><p>跨域问题：浏览器禁止请求的发起者与服务端发生跨域ajax请求，请求被浏览器拦截的问题</p><p>解决方案：CORS。不知道的小伙伴可以查看<a href="https://www.ruanyifeng.com/blog/2016/04/cors.html">https://www.ruanyifeng.com/blog/2016/04/cors.html</a></p><h3 id="❷模拟跨域问题"><a href="#❷模拟跨域问题" class="headerlink" title="❷模拟跨域问题"></a>❷模拟跨域问题</h3><p>编写一个页面，并在VScode中启动 <code>live-server --port=8090</code></p><pre class="line-numbers language-html"><code class="language-html"><span class="token doctype">&lt;!DOCTYPE html></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>html</span> <span class="token attr-name">lang</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>en<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>head</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>meta</span> <span class="token attr-name">charset</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>UTF-8<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>meta</span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>viewport<span class="token punctuation">"</span></span> <span class="token attr-name">content</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>width<span class="token punctuation">=</span>device-width, initial-scale<span class="token punctuation">=</span>1.0<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>meta</span> <span class="token attr-name">http-equiv</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>X-UA-Compatible<span class="token punctuation">"</span></span> <span class="token attr-name">content</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>ie<span class="token punctuation">=</span>edge<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>title</span><span class="token punctuation">></span></span>Document<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>title</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>head</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>body</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>pre</span><span class="token punctuation">></span></span>这是一个测试！<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>pre</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>body</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>https://unpkg.com/axios/dist/axios.min.js<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token script language-javascript"></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">></span></span><span class="token script language-javascript">  axios<span class="token punctuation">.</span><span class="token keyword">get</span><span class="token punctuation">(</span><span class="token string">"http://localhost:10010/user/1?authorization=admin"</span><span class="token punctuation">)</span>  <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span>resp <span class="token operator">=</span><span class="token operator">></span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>resp<span class="token punctuation">.</span>data<span class="token punctuation">)</span><span class="token punctuation">)</span>  <span class="token punctuation">.</span><span class="token keyword">catch</span><span class="token punctuation">(</span>err <span class="token operator">=</span><span class="token operator">></span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span><span class="token punctuation">)</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>html</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>可以在浏览器控制台看到下面的错误：</p><p><img src="https://img.jwt1399.top/img/202209251219308.png"></p><p>从<code>localhost:8090</code>访问<code>localhost:10010</code>，端口不同，显然是跨域的请求。</p><h3 id="❸解决跨域问题"><a href="#❸解决跨域问题" class="headerlink" title="❸解决跨域问题"></a>❸解决跨域问题</h3><p>在gateway服务的application.yml文件中，添加下面的配置：</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">spring</span><span class="token punctuation">:</span>  <span class="token key atrule">cloud</span><span class="token punctuation">:</span>    <span class="token key atrule">gateway</span><span class="token punctuation">:</span>      <span class="token comment" spellcheck="true"># .......</span>      <span class="token key atrule">globalcors</span><span class="token punctuation">:</span> <span class="token comment" spellcheck="true"># 全局的跨域处理</span>        <span class="token key atrule">add-to-simple-url-handler-mapping</span><span class="token punctuation">:</span> <span class="token boolean important">true </span><span class="token comment" spellcheck="true"># 解决options请求被拦截问题</span>        <span class="token key atrule">corsConfigurations</span><span class="token punctuation">:</span>          '<span class="token punctuation">[</span>/**<span class="token punctuation">]</span>'<span class="token punctuation">:</span> <span class="token comment" spellcheck="true"># 拦截一切请求</span>            <span class="token key atrule">allowedOrigins</span><span class="token punctuation">:</span> <span class="token comment" spellcheck="true"># 允许哪些网站的跨域请求 </span>              <span class="token punctuation">-</span> <span class="token string">"http://localhost:8090"</span>              <span class="token punctuation">-</span> <span class="token string">"https://jwt1399.top"</span>            <span class="token key atrule">allowedMethods</span><span class="token punctuation">:</span> <span class="token comment" spellcheck="true"># 允许的跨域ajax的请求方式</span>              <span class="token punctuation">-</span> <span class="token string">"GET"</span>              <span class="token punctuation">-</span> <span class="token string">"POST"</span>              <span class="token punctuation">-</span> <span class="token string">"DELETE"</span>              <span class="token punctuation">-</span> <span class="token string">"PUT"</span>              <span class="token punctuation">-</span> <span class="token string">"OPTIONS"</span>            <span class="token key atrule">allowedHeaders</span><span class="token punctuation">:</span> <span class="token string">"*"</span> <span class="token comment" spellcheck="true"># 允许在请求中携带的头信息</span>            <span class="token key atrule">allowCredentials</span><span class="token punctuation">:</span> <span class="token boolean important">true </span><span class="token comment" spellcheck="true"># 是否允许携带cookie</span>            <span class="token key atrule">maxAge</span><span class="token punctuation">:</span> <span class="token number">360000 </span><span class="token comment" spellcheck="true"># 这次跨域检测的有效期</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h1 id="8-Docker技术"><a href="#8-Docker技术" class="headerlink" title="8.Docker技术"></a>8.Docker技术</h1><p><strong>镜像（Image）</strong>：将应用程序及其所需的依赖、函数库、环境、配置等文件打包在一起，称为镜像</p><p><strong>容器（Container）</strong>：镜像中的应用程序运行后形成的进程就是容器</p><p><strong>仓库（Repository）</strong>：一个镜像托管的服务器，可以从中上传、拉取镜像。</p><p><strong>数据卷（volume）</strong>：是一个虚拟目录，指向宿主机文件系统中的某个目录。</p><h2 id="①镜像操作"><a href="#①镜像操作" class="headerlink" title="①镜像操作"></a>①镜像操作</h2><h3 id="❶镜像名称"><a href="#❶镜像名称" class="headerlink" title="❶镜像名称"></a>❶镜像名称</h3><ul><li>镜名称一般分两部分组成：[repository]:[tag]。</li><li>在没有指定tag时，默认是latest，代表最新版本的镜像</li></ul><h3 id="❷镜像命令"><a href="#❷镜像命令" class="headerlink" title="❷镜像命令"></a>❷镜像命令</h3><p><img src="https://img.jwt1399.top/img/202210031952952.png"></p><pre class="line-numbers language-bash"><code class="language-bash">docker images  <span class="token comment" spellcheck="true"># 查看镜像</span>docker inspect <span class="token comment" spellcheck="true"># 查看具体镜像</span>docker rmi <span class="token comment" spellcheck="true"># 删除镜像</span>docker pull    <span class="token comment" spellcheck="true"># 拉取镜像</span>docker push    <span class="token comment" spellcheck="true"># 推送镜像</span>docker save    <span class="token comment" spellcheck="true"># 导出镜像</span>docker load    <span class="token comment" spellcheck="true"># 加载镜像</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="❸镜像案例"><a href="#❸镜像案例" class="headerlink" title="❸镜像案例"></a>❸镜像案例</h3><blockquote><p>需求：从DockerHub中拉取一个nginx镜像并查看</p></blockquote><pre class="line-numbers language-bash"><code class="language-bash"><span class="token comment" spellcheck="true"># 1）首先去镜像仓库搜索nginx镜像，比如DockerHub</span><span class="token comment" spellcheck="true"># 2）根据查看到的镜像名称，拉取自己需要的镜像</span>docker pull nginx<span class="token comment" spellcheck="true"># 3）查看拉取到的镜像</span>docker images <span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><blockquote><p>需求：利用docker save将nginx镜像导出磁盘，然后再通过load加载回来</p></blockquote><pre class="line-numbers language-bash"><code class="language-bash"><span class="token comment" spellcheck="true"># 1）利用docker xx --help命令查看语法，例如，查看save命令用法，可以输入命令：</span>docker save --help<span class="token comment" spellcheck="true"># 2）使用docker save导出镜像到磁盘 </span>docker save -o nginx.tar nginx:latest<span class="token comment" spellcheck="true"># 3）使用docker load加载镜像</span><span class="token comment" spellcheck="true"># 先删除本地的nginx镜像：</span>docker rmi nginx:latest<span class="token comment" spellcheck="true"># 然后运行命令，加载本地文件</span>docker load -i nginx.tar<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="②容器操作"><a href="#②容器操作" class="headerlink" title="②容器操作"></a>②容器操作</h2><h3 id="❶容器状态"><a href="#❶容器状态" class="headerlink" title="❶容器状态"></a>❶容器状态</h3><p>容器保护三个状态：</p><ul><li>运行：进程正常运行</li><li>暂停：进程暂停，CPU不再运行，并不释放内存</li><li>停止：进程终止，回收进程占用的内存、CPU等资源</li></ul><h3 id="❷容器命令"><a href="#❷容器命令" class="headerlink" title="❷容器命令"></a>❷容器命令</h3><p><img src="https://img.jwt1399.top/img/202210032001294.png"></p><pre class="line-numbers language-bash"><code class="language-bash">docker <span class="token function">ps</span>       <span class="token comment" spellcheck="true"># 查看容器状态, -a 查看所有容器，包括已经停止的</span>docker run      <span class="token comment" spellcheck="true"># 创建并运行一个容器，处于运行状态</span>docker pause    <span class="token comment" spellcheck="true"># 让一个运行的容器暂停</span>docker unpause  <span class="token comment" spellcheck="true"># 让一个容器从暂停状态恢复运行</span>docker stop     <span class="token comment" spellcheck="true"># 停止一个运行的容器</span>docker start    <span class="token comment" spellcheck="true"># 让一个停止的容器再次运行</span>docker <span class="token function">rm</span>       <span class="token comment" spellcheck="true"># 删除一个容器</span>docker <span class="token function">exec</span>     <span class="token comment" spellcheck="true"># 进入容器执行命令</span>docker logs     <span class="token comment" spellcheck="true"># 查看容器日志的命令,-f 参数可以持续查看日志</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="❸容器案例"><a href="#❸容器案例" class="headerlink" title="❸容器案例"></a>❸容器案例</h3><blockquote><p><strong>需求</strong>：创建并运行一个nginx容器</p></blockquote><pre class="line-numbers language-bash"><code class="language-bash">docker run --name mn -p 80:80 -d nginx<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>命令解读：</p><ul><li>docker run ：创建并运行一个容器</li><li>–name : 给容器起一个名字，比如叫做mn</li><li>-p ：将宿主机端口与容器端口映射，冒号左侧是宿主机端口，右侧是容器端口</li><li>-d：后台运行容器</li><li>nginx：镜像名称，例如nginx</li></ul><blockquote><p><strong>需求</strong>：进入Nginx容器，修改HTML文件内容，添加”哈喽，你好呀！”</p></blockquote><p>1）进入容器。进入我们刚刚创建的nginx容器的命令为：</p><pre class="line-numbers language-sh"><code class="language-sh">docker exec -it mn bash<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>命令解读：</p><ul><li><p>docker exec ：进入容器内部，执行一个命令</p></li><li><p>-it : 给当前进入的容器创建一个标准输入、输出终端，允许我们与容器交互</p></li><li><p>mn ：要进入的容器的名称</p></li><li><p>bash：进入容器后执行的命令，bash是一个linux终端交互命令</p></li></ul><p>2）进入nginx的HTML所在目录</p><p>查看DockerHub网站中的nginx页面，可以知道nginx的html目录位置在<code>/usr/share/nginx/html</code></p><pre class="line-numbers language-bash"><code class="language-bash"><span class="token function">cd</span> /usr/share/nginx/html<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>3）修改index.html的内容</p><p>容器内没有vi命令，无法直接修改，我们用下面的命令来修改：</p><pre class="line-numbers language-sh"><code class="language-sh">sed -i -e 's#Welcome to nginx#哈喽，你好呀！#g' -e 's#<head>#<head><meta charset="utf-8">#g' index.html<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>在浏览器访问 <code>127.0.0.1:80</code> ，即可看到结果</p><h2 id="③数据卷"><a href="#③数据卷" class="headerlink" title="③数据卷"></a>③数据卷</h2><h3 id="❶简介"><a href="#❶简介" class="headerlink" title="❶简介"></a>❶简介</h3><p><strong>数据卷（volume）</strong>是一个虚拟目录，指向宿主机文件系统中的某个目录。</p><p>数据卷的作用：将容器与数据分离，解耦合，方便操作容器内数据，保证数据安全</p><p><img src="https://img.jwt1399.top/img/202210032028213.png"></p><p>一旦完成数据卷挂载，对容器的一切操作都会作用在数据卷对应的宿主机目录了。这样，我们操作宿主机的&#x2F;var&#x2F;lib&#x2F;docker&#x2F;volumes&#x2F;html目录，就等于操作容器内的&#x2F;usr&#x2F;share&#x2F;nginx&#x2F;html目录了</p><h3 id="❷数据集命令"><a href="#❷数据集命令" class="headerlink" title="❷数据集命令"></a>❷数据集命令</h3><pre class="line-numbers language-sh"><code class="language-sh">docker volume create     # 创建一个数据卷docker volume ls         # 列出所有的volumedocker volume inspect    # 显示一个或多个volume的信息docker volume rm         # 删除一个或多个指定的volumedocker volume prune      # 删除未使用的volume<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="❸创建和查看数据卷"><a href="#❸创建和查看数据卷" class="headerlink" title="❸创建和查看数据卷"></a>❸创建和查看数据卷</h3><p><strong>需求</strong>：创建一个数据卷，并查看数据卷在宿主机的目录位置</p><p>① 创建数据卷</p><pre class="line-numbers language-sh"><code class="language-sh">docker volume create html<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>② 查看所有数据</p><pre class="line-numbers language-sh"><code class="language-sh">docker volume ls<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>③ 查看数据卷详细信息卷</p><pre class="line-numbers language-sh"><code class="language-sh">docker volume inspect html<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p><img src="https://img.jwt1399.top/img/202210032037488.png"></p><p>可以看到，我们创建的html这个数据卷关联的宿主机目录为<code>/var/lib/docker/volumes/html/_data</code>目录。</p><h3 id="❹挂载数据卷"><a href="#❹挂载数据卷" class="headerlink" title="❹挂载数据卷"></a>❹挂载数据卷</h3><p>我们在创建容器时，可以通过 -v 参数来挂载一个数据卷到某个容器内目录，命令格式如下：</p><pre class="line-numbers language-sh"><code class="language-sh">docker run \  --name mn \  -v html:/root/html \  -p 8080:80  nginx \<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>这里的-v就是挂载数据卷的命令：</p><ul><li><code>-v html:/root/html</code> ：把html数据卷挂载到容器内的&#x2F;root&#x2F;html这个目录中</li></ul><h3 id="❺案例-给Nginx挂载数据卷"><a href="#❺案例-给Nginx挂载数据卷" class="headerlink" title="❺案例:给Nginx挂载数据卷"></a>❺案例:给Nginx挂载数据卷</h3><blockquote><p><strong>需求</strong>：创建一个nginx容器，修改容器内的html目录内的index.html内容</p></blockquote><p>① 创建容器并挂载数据卷到容器内的HTML目录</p><pre class="line-numbers language-sh"><code class="language-sh">docker run --name mn -v html:/usr/share/nginx/html -p 80:80 -d nginx<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>② 进入html数据卷所在位置，并修改HTML内容</p><pre class="line-numbers language-sh"><code class="language-sh"># 查看html数据卷的位置docker volume inspect html# 进入该目录cd /var/lib/docker/volumes/html/_data# 修改文件vi index.html<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>注：mac下 docker 实际是在vm里又加了一层，因此需要进入 vm 才能执行上面操作</p><pre class="line-numbers language-bash"><code class="language-bash">docker run -it --privileged --pid<span class="token operator">=</span>host justincormack/nsenter1<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><h3 id="❻案例-给MySQL挂载本地目录"><a href="#❻案例-给MySQL挂载本地目录" class="headerlink" title="❻案例-给MySQL挂载本地目录"></a>❻案例-给MySQL挂载本地目录</h3><p>容器不仅仅可以挂载数据卷，也可以直接挂载到宿主机目录上。关联关系如下：</p><ul><li>带数据卷模式：宿主机目录 –&gt; 数据卷 —&gt; 容器内目录</li><li>直接挂载模式：宿主机目录 —&gt; 容器内目录</li></ul><p><img src="https://img.jwt1399.top/img/202210032118037.png"></p><p><strong>语法</strong>：目录挂载与数据卷挂载的语法是类似的：</p><ul><li>-v [宿主机目录]:[容器内目录]</li><li>-v [宿主机文件]:[容器内文件]</li></ul><blockquote><p><strong>需求</strong>：创建并运行一个MySQL容器，将宿主机目录直接挂载到容器</p></blockquote><p>1）拉取mysql镜像</p><pre class="line-numbers language-bash"><code class="language-bash">docker pull mysql<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>2）创建目录&#x2F;tmp&#x2F;mysql&#x2F;data</p><pre class="line-numbers language-bash"><code class="language-bash"><span class="token function">mkdir</span> -p /tmp/mysql/data<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>3）创建目录&#x2F;tmp&#x2F;mysql&#x2F;conf，将提供的hmy.cnf文件上传到&#x2F;tmp&#x2F;mysql&#x2F;conf</p><pre class="line-numbers language-bash"><code class="language-bash"><span class="token function">mkdir</span> -p /tmp/mysql/conf<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>4）去DockerHub查阅资料，创建并运行MySQL容器，要求：</p><p>① 挂载&#x2F;tmp&#x2F;mysql&#x2F;data到mysql容器内数据存储目录</p><p>② 挂载&#x2F;tmp&#x2F;mysql&#x2F;conf&#x2F;hmy.cnf到mysql容器的配置文件</p><p>③ 设置MySQL密码</p><pre class="line-numbers language-bash"><code class="language-bash">docker run \ --name some-mysql \ -e MYSQL_ROOT_PASSWORD<span class="token operator">=</span>root \ -p 3306:3306 \ -v /tmp/mysql/data:/var/lib/mysql \ -v /tmp/mysql/conf/hmy.cnf:/etc/mysql/conf.d/hmy.cnf \ -d mysql:latest <span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>数据卷挂载与目录直接挂载的区别</p><ul><li>数据卷挂载耦合度低，由docker来管理目录，但是目录较深，不好找</li><li>目录挂载耦合度高，需要我们自己管理目录，不过目录容易寻找查看</li></ul><h2 id="④Dockerfile"><a href="#④Dockerfile" class="headerlink" title="④Dockerfile"></a>④Dockerfile</h2><p>常见的镜像在DockerHub就能找到，但是我们自己写的项目就必须自己构建镜像了。</p><h3 id="❶镜像结构"><a href="#❶镜像结构" class="headerlink" title="❶镜像结构"></a>❶镜像结构</h3><p>要自定义镜像，就必须先了解镜像的结构才行。镜像是将应用程序及其需要的系统函数库、环境、配置、依赖打包而成。</p><p>我们以MySQL为例，来看看镜像的组成结构：</p><p><img src="https://img.jwt1399.top/img/202210041351237.png"></p><p>简单来说，镜像就是在系统函数库、运行环境基础上，添加应用程序文件、配置文件、依赖文件等组合，然后编写好启动脚本打包在一起形成的文件。</p><p>因此我们只需要告诉Docker，我们的镜像的组成，需要哪些BaseImage、需要拷贝什么文件、需要安装什么依赖、启动脚本是什么，将来Docker会帮助我们构建镜像。而描述这些信息的文件就是Dockerfile文件。</p><h3 id="❷语法"><a href="#❷语法" class="headerlink" title="❷语法"></a>❷语法</h3><p><strong>Dockerfile</strong>就是一个文本文件，其中包含一个个的**指令(Instruction)**，用指令来说明要执行什么操作来构建镜像。每一个指令都会形成一层Layer。</p><p><img src="/../images/SpringCloud-%E5%AE%9E%E7%94%A8%E7%AF%87/image-20221004135210763.png"></p><p>更新详细语法说明，请参考官网文档： <a href="https://docs.docker.com/engine/reference/builder">https://docs.docker.com/engine/reference/builder</a></p><h3 id="❸案例"><a href="#❸案例" class="headerlink" title="❸案例"></a>❸案例</h3><h4 id="⓵基于Ubuntu构建Java项目"><a href="#⓵基于Ubuntu构建Java项目" class="headerlink" title="⓵基于Ubuntu构建Java项目"></a>⓵基于Ubuntu构建Java项目</h4><blockquote><p>需求：基于Ubuntu镜像构建一个新镜像，运行一个java项目</p></blockquote><p>步骤1：新建一个空文件夹 docker-demo</p><p>步骤2：拷贝 docker-demo.jar 文件到 docker-demo 目录</p><p>步骤3：拷贝 jdk8.tar.gz 文件到 docker-demo 目录</p><p>步骤4：在 docker-demo 目录下新建 Dockerfile，内容如下</p><pre class="line-numbers language-bash"><code class="language-bash"><span class="token comment" spellcheck="true"># 指定基础镜像</span>FROM ubuntu:16.04<span class="token comment" spellcheck="true"># 配置环境变量，JDK的安装目录</span>ENV JAVA_DIR<span class="token operator">=</span>/usr/local<span class="token comment" spellcheck="true"># 拷贝jdk和java项目的包</span>COPY ./jdk8.tar.gz <span class="token variable">$JAVA_DIR</span>/COPY ./docker-demo.jar /tmp/app.jar<span class="token comment" spellcheck="true"># 安装JDK</span>RUN <span class="token function">cd</span> <span class="token variable">$JAVA_DIR</span> \ <span class="token operator">&amp;&amp;</span> <span class="token function">tar</span> -xf ./jdk8.tar.gz \ <span class="token operator">&amp;&amp;</span> <span class="token function">mv</span> ./jdk1.8.0_144 ./java8<span class="token comment" spellcheck="true"># 配置环境变量</span>ENV JAVA_HOME<span class="token operator">=</span><span class="token variable">$JAVA_DIR</span>/java8ENV PATH<span class="token operator">=</span><span class="token variable">$PATH</span><span class="token keyword">:</span><span class="token variable">$JAVA_HOME</span>/bin<span class="token comment" spellcheck="true"># 暴露端口</span>EXPOSE 8090<span class="token comment" spellcheck="true"># 入口，java项目的启动命令</span>ENTRYPOINT java -jar /tmp/app.jar<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>步骤5：进入 docker-demo，运行命令</p><pre class="line-numbers language-bash"><code class="language-bash">docker build -t javaweb:1.0 <span class="token keyword">.</span><span class="token comment" spellcheck="true"># -t代表tag即名字</span><span class="token comment" spellcheck="true"># .代表Dockerfile所在的目录</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>步骤6：使用docker run创建容器并运行</p><pre class="line-numbers language-bash"><code class="language-bash">docker run --name web -p 8090:8090 -d javaweb:1.0 <span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>最后访问 <a href="http://127.0.0.1:8090/hello/count">http://127.0.0.1:8090/hello/count</a></p><h4 id="⓶基于java8构建Java项目"><a href="#⓶基于java8构建Java项目" class="headerlink" title="⓶基于java8构建Java项目"></a>⓶基于java8构建Java项目</h4><p>虽然我们可以基于Ubuntu基础镜像，添加任意自己需要的安装包，构建镜像，但是却比较麻烦。所以大多数情况下，我们都可以在一些安装了部分软件的基础镜像上做改造。例如，构建java项目的镜像，可以在已经准备了JDK的基础镜像基础上构建。</p><blockquote><p>需求：基于java:8-alpine镜像，将一个Java项目构建为镜像</p></blockquote><p>① 新建一个空文件夹 docker-demo</p><p>② 拷贝 docker-demo.jar 到这个目录中</p><p>③ 在目录中新建 Dockerfile 文件，内容如下：</p><pre class="line-numbers language-bash"><code class="language-bash">FROM java:8-alpineCOPY ./docker-demo.jar /tmp/app.jarEXPOSE 8090ENTRYPOINT java -jar /tmp/app.jar<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><ul><li>④ 使用docker build命令构建镜像</li></ul><pre class="line-numbers language-bash"><code class="language-bash">docker build -t javaweb:2.0 <span class="token keyword">.</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><ul><li>⑤ 使用docker run创建容器并运行</li></ul><pre class="line-numbers language-bash"><code class="language-bash">docker run --name web -p 8090:8090 -d javaweb:2.0 <span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><h2 id="⑤DockerCompose"><a href="#⑤DockerCompose" class="headerlink" title="⑤DockerCompose"></a>⑤DockerCompose</h2><h3 id="❶简介-1"><a href="#❶简介-1" class="headerlink" title="❶简介"></a>❶简介</h3><p>在线上环境中，通常不会将项目的所有组件放到同一个容器中；更好的做法是<strong>把每个独立的功能装进单独的容器</strong>，这样方便复用。因此同一个服务器上会运行着多个容器，如果每次都靠一条条指令去启动，未免也太繁琐了。 <code>Docker-compose</code> 就是解决这个问题的，它用来编排多个容器，将启动容器的命令统一写到 <code>docker-compose.yml</code> 文件中，以后每次启动这一组容器时，只需要 <code>docker-compose up</code> 就可以了。</p><p>其实DockerCompose文件可以看做是将多个docker run命令写到一个文件，只是语法稍有差异。</p><p>Compose文件是一个文本文件，通过指令定义集群中的每个容器如何运行。格式如下：</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">version</span><span class="token punctuation">:</span> <span class="token string">"3.8"</span> <span class="token key atrule">services</span><span class="token punctuation">:</span>  mysql<span class="token punctuation">:</span>    image<span class="token punctuation">:</span> mysql<span class="token punctuation">:</span>5.7.25    <span class="token key atrule">environment</span><span class="token punctuation">:</span>     <span class="token key atrule">MYSQL_ROOT_PASSWORD</span><span class="token punctuation">:</span> root     volumes<span class="token punctuation">:</span>     <span class="token punctuation">-</span> <span class="token string">"/tmp/mysql/data:/var/lib/mysql"</span>     <span class="token punctuation">-</span> <span class="token string">"/tmp/mysql/conf/hmy.cnf:/etc/mysql/conf.d/hmy.cnf"</span>  web<span class="token punctuation">:</span>    build<span class="token punctuation">:</span> .    ports<span class="token punctuation">:</span>     <span class="token punctuation">-</span> <span class="token string">"8090:8090"</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>上面的Compose文件就描述一个项目，其中包含两个容器：</p><ul><li>mysql：一个基于<code>mysql:5.7.25</code>镜像构建的容器，并且挂载了两个目录</li><li>web：一个基于<code>docker build</code>临时构建的镜像容器，映射端口时8090</li></ul><p>DockerCompose的详细语法参考官网：<a href="https://docs.docker.com/compose/compose-file/">https://docs.docker.com/compose/compose-file/</a></p><h3 id="❷案例：部署微服务集群"><a href="#❷案例：部署微服务集群" class="headerlink" title="❷案例：部署微服务集群"></a>❷案例：部署微服务集群</h3><blockquote><p><strong>需求</strong>：将之前学习的cloud-demo微服务集群利用DockerCompose部署</p></blockquote><p><strong>实现思路</strong>：</p><p>① 在cloud-demo文件夹编写docker-compose文件</p><p>② 修改自己的cloud-demo项目，将数据库、nacos地址都命名为docker-compose中的服务名</p><p>③ 使用maven打包工具，将项目中的每个微服务都打包为app.jar</p><p>④ 将打包好的app.jar拷贝到cloud-demo中的每一个对应的子目录中</p><p>⑤ 将cloud-demo上传至虚拟机，利用 docker-compose up -d 来部署</p><h4 id="⓵编写docker-compose文件"><a href="#⓵编写docker-compose文件" class="headerlink" title="⓵编写docker-compose文件"></a>⓵编写docker-compose文件</h4><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">version</span><span class="token punctuation">:</span> <span class="token string">"3.2"</span> <span class="token comment" spellcheck="true">#docker-compose版本</span><span class="token key atrule">services</span><span class="token punctuation">:</span>  <span class="token key atrule">nacos</span><span class="token punctuation">:</span>    <span class="token key atrule">image</span><span class="token punctuation">:</span> nacos/nacos<span class="token punctuation">-</span>server    <span class="token key atrule">environment</span><span class="token punctuation">:</span>      <span class="token key atrule">MODE</span><span class="token punctuation">:</span> standalone    <span class="token key atrule">ports</span><span class="token punctuation">:</span>      <span class="token punctuation">-</span> <span class="token string">"8848:8848"</span>  <span class="token key atrule">mysql</span><span class="token punctuation">:</span>    <span class="token key atrule">image</span><span class="token punctuation">:</span> mysql<span class="token punctuation">:</span>5.7.25    <span class="token key atrule">environment</span><span class="token punctuation">:</span>      <span class="token key atrule">MYSQL_ROOT_PASSWORD</span><span class="token punctuation">:</span> <span class="token number">123</span>    <span class="token key atrule">volumes</span><span class="token punctuation">:</span>      <span class="token punctuation">-</span> <span class="token string">"$PWD/mysql/data:/var/lib/mysql"</span>      <span class="token punctuation">-</span> <span class="token string">"$PWD/mysql/conf:/etc/mysql/conf.d/"</span>  <span class="token key atrule">userservice</span><span class="token punctuation">:</span>    <span class="token key atrule">build</span><span class="token punctuation">:</span> ./user<span class="token punctuation">-</span>service  <span class="token key atrule">orderservice</span><span class="token punctuation">:</span>    <span class="token key atrule">build</span><span class="token punctuation">:</span> ./order<span class="token punctuation">-</span>service  <span class="token key atrule">gateway</span><span class="token punctuation">:</span>    <span class="token key atrule">build</span><span class="token punctuation">:</span> ./gateway    <span class="token key atrule">ports</span><span class="token punctuation">:</span>      <span class="token punctuation">-</span> <span class="token string">"10010:10010"</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>可以看到，其中包含5个service服务：</p><ul><li><code>nacos</code>：作为注册中心和配置中心<ul><li><code>image: nacos/nacos-server</code>： 基于nacos&#x2F;nacos-server镜像构建</li><li><code>environment</code>：环境变量<ul><li><code>MODE: standalone</code>：单点模式启动</li></ul></li><li><code>ports</code>：端口映射，这里暴露了8848端口</li></ul></li><li><code>mysql</code>：数据库<ul><li><code>image: mysql:5.7.25</code>：镜像版本是mysql:5.7.25</li><li><code>environment</code>：环境变量<ul><li><code>MYSQL_ROOT_PASSWORD: root</code>：设置数据库root账户的密码为root</li></ul></li><li><code>volumes</code>：数据卷挂载，这里挂载了mysql的data、conf目录</li></ul></li><li><code>userservice</code>、<code>orderservice</code>、<code>gateway</code>：都是基于Dockerfile临时构建的</li></ul><h4 id="⓶修改微服务配置"><a href="#⓶修改微服务配置" class="headerlink" title="⓶修改微服务配置"></a>⓶修改微服务配置</h4><p>因为微服务将来要部署为docker容器，而容器之间互联不是通过IP地址，而是通过容器名。这里我们将order-service、user-service、gateway服务的mysql、nacos地址都修改为基于容器名的访问。如下所示：</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">spring</span><span class="token punctuation">:</span>  <span class="token key atrule">datasource</span><span class="token punctuation">:</span>    <span class="token key atrule">url</span><span class="token punctuation">:</span> jdbc<span class="token punctuation">:</span>mysql<span class="token punctuation">:</span>//mysql<span class="token punctuation">:</span>3306/cloud_order<span class="token punctuation">?</span>useSSL=false <span class="token comment" spellcheck="true"># mysql</span>    <span class="token key atrule">username</span><span class="token punctuation">:</span> root    <span class="token key atrule">password</span><span class="token punctuation">:</span> root    <span class="token key atrule">driver-class-name</span><span class="token punctuation">:</span> com.mysql.jdbc.Driver  <span class="token key atrule">application</span><span class="token punctuation">:</span>    <span class="token key atrule">name</span><span class="token punctuation">:</span> orderservice  <span class="token key atrule">cloud</span><span class="token punctuation">:</span>    <span class="token key atrule">nacos</span><span class="token punctuation">:</span>      <span class="token key atrule">server-addr</span><span class="token punctuation">:</span> nacos<span class="token punctuation">:</span><span class="token number">8848 </span><span class="token comment" spellcheck="true"># nacos服务地址</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="⓷打包微服务模块"><a href="#⓷打包微服务模块" class="headerlink" title="⓷打包微服务模块"></a>⓷打包微服务模块</h4><p>将每个微服务都打包。因为之前查看到Dockerfile中的jar包名称都是app.jar，因此我们的每个微服务都需要用这个名称。可以通过修改pom.xml中的打包名称来实现，每个微服务都需要修改</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>build</span><span class="token punctuation">></span></span>  <span class="token comment" spellcheck="true">&lt;!-- 服务打包的最终名称 --></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>finalName</span><span class="token punctuation">></span></span>app<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>finalName</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>plugins</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>plugin</span><span class="token punctuation">></span></span>      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework.boot<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-boot-maven-plugin<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>plugin</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>plugins</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>build</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="⓸编写Dockerfile"><a href="#⓸编写Dockerfile" class="headerlink" title="⓸编写Dockerfile"></a>⓸编写Dockerfile</h4><p>将每个微服务构建成镜像，编写三个Dockerfile，放入对应目录</p><pre class="line-numbers language-bash"><code class="language-bash">FROM java:8-alpineCOPY ./app.jar /tmp/app.jarENTRYPOINT java -jar /tmp/app.jar<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>编译打包好的app.jar文件，需要放到Dockerfile的同级目录中。注意：每个微服务的app.jar放到与服务名称对应的目录，别搞错了。</p><pre class="line-numbers language-bash"><code class="language-bash"><span class="token keyword">.</span>├── docker-compose.yml├── gateway│   ├── Dockerfile│   └── app.jar├── mysql│   ├── conf│   └── data├── order-service│   ├── Dockerfile│   └── app.jar└── user-service    ├── Dockerfile    └── app.jar<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="⓹部署"><a href="#⓹部署" class="headerlink" title="⓹部署"></a>⓹部署</h4><p>最后，我们需要将整个cloud-demo文件夹上传到虚拟机中，由DockerCompose部署。</p><p>进入cloud-demo目录，然后运行下面的命令：</p><pre class="line-numbers language-sh"><code class="language-sh">docker-compose up -d   # 后台运行容器<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>其它命令</p><pre class="line-numbers language-bash"><code class="language-bash">docker-compose build   <span class="token comment" spellcheck="true"># 重新构建镜像</span>docker-compose start   <span class="token comment" spellcheck="true"># 启动已有的容器</span>docker-compose stop    <span class="token comment" spellcheck="true"># 停止已有的容器</span>docker-compose logs    <span class="token comment" spellcheck="true"># 查看容器日志</span>docker-compose down    <span class="token comment" spellcheck="true"># 删除容器</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="⑥私有仓库"><a href="#⑥私有仓库" class="headerlink" title="⑥私有仓库"></a>⑥私有仓库</h2><p>使用DockerCompose部署带有图象界面的DockerRegistry，命令如下：</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">version</span><span class="token punctuation">:</span> <span class="token string">'3.0'</span><span class="token key atrule">services</span><span class="token punctuation">:</span>  <span class="token key atrule">registry</span><span class="token punctuation">:</span>    <span class="token key atrule">image</span><span class="token punctuation">:</span> registry    <span class="token key atrule">volumes</span><span class="token punctuation">:</span>      <span class="token punctuation">-</span> ./registry<span class="token punctuation">-</span>data<span class="token punctuation">:</span>/var/lib/registry  <span class="token key atrule">ui</span><span class="token punctuation">:</span>    <span class="token key atrule">image</span><span class="token punctuation">:</span> joxit/docker<span class="token punctuation">-</span>registry<span class="token punctuation">-</span>ui<span class="token punctuation">:</span>static    <span class="token key atrule">ports</span><span class="token punctuation">:</span>      <span class="token punctuation">-</span> 8080<span class="token punctuation">:</span><span class="token number">80</span>    <span class="token key atrule">environment</span><span class="token punctuation">:</span>      <span class="token punctuation">-</span> REGISTRY_TITLE=简简私有仓库      <span class="token punctuation">-</span> REGISTRY_URL=http<span class="token punctuation">:</span>//registry<span class="token punctuation">:</span><span class="token number">5000</span>    <span class="token key atrule">depends_on</span><span class="token punctuation">:</span>      <span class="token punctuation">-</span> registry<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="❶配置Docker信任地址"><a href="#❶配置Docker信任地址" class="headerlink" title="❶配置Docker信任地址"></a>❶配置Docker信任地址</h3><p>我们的私服采用的是http协议，默认使用HTTPS推送镜像，http不被Docker信任，所以需要做一个配置</p><pre class="line-numbers language-sh"><code class="language-sh"># 打开要修改的文件vi /etc/docker/daemon.json# 添加内容："insecure-registries":["http://192.168.150.101:8080"]# 重加载systemctl daemon-reload# 重启dockersystemctl restart docker<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>访问<code>http://192.168.150.101:8080</code> 即可</p><h3 id="❷推送、拉取镜像"><a href="#❷推送、拉取镜像" class="headerlink" title="❷推送、拉取镜像"></a>❷推送、拉取镜像</h3><p>推送镜像到私有镜像服务必须先tag，步骤如下：</p><p>① 重命名本地镜像，名称前缀为私有仓库的地址：192.168.150.101:8080&#x2F;</p><pre class="line-numbers language-sh"><code class="language-sh">docker tag nginx:latest 192.168.150.101:8080/nginx:1.0 <span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>② 推送镜像</p><pre class="line-numbers language-sh"><code class="language-sh">docker push 192.168.150.101:8080/nginx:1.0 <span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>③ 拉取镜像</p><pre class="line-numbers language-sh"><code class="language-sh">docker pull 192.168.150.101:8080/nginx:1.0 <span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><h1 id="9-RabbitMQ"><a href="#9-RabbitMQ" class="headerlink" title="9.RabbitMQ"></a>9.RabbitMQ</h1><h2 id="①初识MQ"><a href="#①初识MQ" class="headerlink" title="①初识MQ"></a>①初识MQ</h2><h3 id="❶同步和异步"><a href="#❶同步和异步" class="headerlink" title="❶同步和异步"></a>❶同步和异步</h3><ul><li><p>同步通讯：就像打电话，需要实时响应。</p></li><li><p>异步通讯：就像发微信，不需要马上回复。</p></li></ul><p><img src="https://img.jwt1399.top/img/202210051307025.png"></p><p>两种方式各有优劣，打电话可以立即得到响应，但是你却不能跟多个人同时通话。发送微信可以同时与多人交流，但是往往响应会有延迟。</p><h4 id="同步通讯"><a href="#同步通讯" class="headerlink" title="同步通讯"></a>同步通讯</h4><p>优点：</p><ul><li>时效性较强，可以立即得到结果</li></ul><p>缺点：</p><ul><li>耦合度高</li><li>性能和吞吐能力下降</li><li>有额外的资源消耗</li><li>有级联失败问题</li></ul><h4 id="异步通讯"><a href="#异步通讯" class="headerlink" title="异步通讯"></a>异步通讯</h4><p>为了解除事件发布者与接收者之间的耦合，两者并不是直接通信，而是有一个中间人（Broker）。发布者发布事件到Broker，不关心谁来订阅事件。接收者从Broker订阅事件，不关心谁发来的消息。</p><p><img src="https://img.jwt1399.top/img/202210051316444.png"></p><p>Broker 是一个像数据总线一样的东西，所有的服务要接收数据和发送数据都发到这个总线上，这个总线就像协议一样，让服务间的通讯变得标准和可控。</p><p>优点：</p><ul><li><p>吞吐量提升：无需等待接收者处理完成，响应更快速</p></li><li><p>故障隔离：服务没有直接调用，不存在级联失败问题</p></li><li><p>调用间没有阻塞，不会造成无效的资源占用</p></li><li><p>耦合度极低：每个服务都可以灵活插拔，可替换</p></li><li><p>流量削峰：不管发布事件的流量波动多大，都由Broker接收，接收者可以按照自己的速度去处理事件</p></li></ul><p>缺点：</p><ul><li>架构复杂了，业务没有明显的流程线，不好管理</li><li>需要依赖于Broker的可靠、安全、性能</li></ul><h3 id="❷消息队列MQ"><a href="#❷消息队列MQ" class="headerlink" title="❷消息队列MQ"></a>❷消息队列MQ</h3><p>比较常见的一种 Broker 就是 MQ 技术，中文是消息队列（MessageQueue），字面来看就是存放消息的队列。</p><p>比较常见的MQ实现：ActiveMQ、RabbitMQ、RocketMQ、Kafka</p><table><thead><tr><th></th><th><strong>RabbitMQ</strong></th><th><strong>ActiveMQ</strong></th><th><strong>RocketMQ</strong></th><th><strong>Kafka</strong></th></tr></thead><tbody><tr><td>维护者</td><td>Rabbit</td><td>Apache</td><td>阿里</td><td>Apache</td></tr><tr><td>开发语言</td><td>Erlang</td><td>Java</td><td>Java</td><td>Scala&amp;Java</td></tr><tr><td>协议支持</td><td>AMQP，XMPP，SMTP，STOMP</td><td>OpenWire，STOMP，REST，XMPP，AMQP</td><td>自定义协议</td><td>自定义协议</td></tr><tr><td>可用性</td><td>高</td><td>一般</td><td>高</td><td>高</td></tr><tr><td>吞吐量</td><td>一般</td><td>差</td><td>高</td><td>非常高</td></tr><tr><td>消息延迟</td><td>微秒级</td><td>毫秒级</td><td>毫秒级</td><td>毫秒以内</td></tr><tr><td>消息可靠性</td><td>高</td><td>一般</td><td>高</td><td>一般</td></tr></tbody></table><p>追求可用性：Kafka、 RocketMQ 、RabbitMQ</p><p>追求可靠性：RabbitMQ、RocketMQ</p><p>追求吞吐能力：RocketMQ、Kafka</p><p>追求消息低延迟：RabbitMQ、Kafka</p><h3 id="❸RabbitMQ结构"><a href="#❸RabbitMQ结构" class="headerlink" title="❸RabbitMQ结构"></a>❸RabbitMQ结构</h3><table><thead><tr><th>MQ成员</th><th>描述</th></tr></thead><tbody><tr><td>publisher</td><td>生产者</td></tr><tr><td>consumer</td><td>消费者</td></tr><tr><td>exchange</td><td>交换机，负责消息路由</td></tr><tr><td>queue</td><td>队列，存储消息</td></tr><tr><td>virtualHost</td><td>虚拟主机，隔离不同租户的 exchange、queue</td></tr></tbody></table><p><img src="https://img.jwt1399.top/img/202210051322263.png"></p><h3 id="❹RabbitMQ消息模型"><a href="#❹RabbitMQ消息模型" class="headerlink" title="❹RabbitMQ消息模型"></a>❹RabbitMQ消息模型</h3><p>RabbitMQ官方提供了5个不同的Demo示例，对应了不同的消息模型</p><ul><li>基本消息队列（BasicQueue）</li><li>工作消息队列（WorkQueue）</li><li>发布订阅模式（Publish&#x2F;Subscribe）<ul><li>广播（Fanout Exchange）</li><li>路由（Direct Exchange）</li><li>主题（Topic Exchange）</li></ul></li></ul><h2 id="②快速入门-1"><a href="#②快速入门-1" class="headerlink" title="②快速入门"></a>②快速入门</h2><h3 id="❶安装RabbitMQ"><a href="#❶安装RabbitMQ" class="headerlink" title="❶安装RabbitMQ"></a>❶安装RabbitMQ</h3><ul><li>拉取镜像</li></ul><pre class="line-numbers language-bash"><code class="language-bash">docker pull rabbitmq:3-management<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><ul><li>运行镜像</li></ul><pre class="line-numbers language-bash"><code class="language-bash">docker run \ -e RABBITMQ_DEFAULT_USER<span class="token operator">=</span>jianjian \ -e RABBITMQ_DEFAULT_PASS<span class="token operator">=</span>123321 \ --name mq \ --hostname mq1 \ -p 15672:15672 \ -p 5672:5672 \ -d rabbitmq:3-management<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>访问 <code>http://127.0.0.1:15672</code> 即可</p><h3 id="❷入门案例"><a href="#❷入门案例" class="headerlink" title="❷入门案例"></a>❷入门案例</h3><p>基本消息队列模式的模型图：</p><p><img src="https://img.jwt1399.top/img/202210051435751.png"></p><p>官方的HelloWorld是基于最基础的消息队列模型来实现的，只包括三个角色：</p><ul><li>publisher：消息发布者，将消息发送到队列queue</li><li>queue：消息队列，负责接受并缓存消息</li><li>consumer：订阅队列，处理队列中的消息</li></ul><h4 id="⓵准备工作"><a href="#⓵准备工作" class="headerlink" title="⓵准备工作"></a>⓵准备工作</h4><p>IDEA中导入Demo工程 <a href="https://jwt1399.lanzouv.com/iw6lR0d7tmob">mq-demo</a>，导入后结构如下：</p><p><img src="https://img.jwt1399.top/img/202210051432528.png"></p><p>包括三部分：</p><ul><li>mq-demo：父工程，管理项目依赖</li><li>publisher：消息的发送者</li><li>consumer：消息的消费者</li></ul><h4 id="⓶publisher实现"><a href="#⓶publisher实现" class="headerlink" title="⓶publisher实现"></a>⓶publisher实现</h4><p>思路：</p><ul><li>1.建立连接</li><li>2.创建Channel</li><li>3.声明队列</li><li>4.发送消息</li><li>5.关闭连接和通道</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">PublisherTest</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Test</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">testSendMessage</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">throws</span> IOException<span class="token punctuation">,</span> TimeoutException <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 1.建立连接</span>        ConnectionFactory factory <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ConnectionFactory</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 1.1.设置连接参数，分别是：主机名、端口号、vhost、用户名、密码</span>        factory<span class="token punctuation">.</span><span class="token function">setHost</span><span class="token punctuation">(</span><span class="token string">"192.168.50.86"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        factory<span class="token punctuation">.</span><span class="token function">setPort</span><span class="token punctuation">(</span><span class="token number">5672</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        factory<span class="token punctuation">.</span><span class="token function">setVirtualHost</span><span class="token punctuation">(</span><span class="token string">"/"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        factory<span class="token punctuation">.</span><span class="token function">setUsername</span><span class="token punctuation">(</span><span class="token string">"jianjian"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        factory<span class="token punctuation">.</span><span class="token function">setPassword</span><span class="token punctuation">(</span><span class="token string">"123321"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 1.2.建立连接</span>        Connection connection <span class="token operator">=</span> factory<span class="token punctuation">.</span><span class="token function">newConnection</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 2.创建通道Channel</span>        Channel channel <span class="token operator">=</span> connection<span class="token punctuation">.</span><span class="token function">createChannel</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 3.创建队列</span>        String queueName <span class="token operator">=</span> <span class="token string">"simple.queue"</span><span class="token punctuation">;</span>        channel<span class="token punctuation">.</span><span class="token function">queueDeclare</span><span class="token punctuation">(</span>queueName<span class="token punctuation">,</span> <span class="token boolean">false</span><span class="token punctuation">,</span> <span class="token boolean">false</span><span class="token punctuation">,</span> <span class="token boolean">false</span><span class="token punctuation">,</span> null<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 4.发送消息</span>        String message <span class="token operator">=</span> <span class="token string">"hello, rabbitmq!"</span><span class="token punctuation">;</span>        channel<span class="token punctuation">.</span><span class="token function">basicPublish</span><span class="token punctuation">(</span><span class="token string">""</span><span class="token punctuation">,</span> queueName<span class="token punctuation">,</span> null<span class="token punctuation">,</span> message<span class="token punctuation">.</span><span class="token function">getBytes</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"发送消息成功：【"</span> <span class="token operator">+</span> message <span class="token operator">+</span> <span class="token string">"】"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 5.关闭通道和连接</span>        channel<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        connection<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="⓷consumer实现"><a href="#⓷consumer实现" class="headerlink" title="⓷consumer实现"></a>⓷consumer实现</h4><p>思路：</p><ul><li>1.建立连接</li><li>2.创建Channel</li><li>3.声明队列</li><li>4.订阅消息</li><li>5.处理消息</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ConsumerTest</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token keyword">throws</span> IOException<span class="token punctuation">,</span> TimeoutException <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 1.建立连接</span>        ConnectionFactory factory <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ConnectionFactory</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 1.1.设置连接参数，分别是：主机名、端口号、vhost、用户名、密码</span>        factory<span class="token punctuation">.</span><span class="token function">setHost</span><span class="token punctuation">(</span><span class="token string">"192.168.50.86"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        factory<span class="token punctuation">.</span><span class="token function">setPort</span><span class="token punctuation">(</span><span class="token number">5672</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        factory<span class="token punctuation">.</span><span class="token function">setVirtualHost</span><span class="token punctuation">(</span><span class="token string">"/"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        factory<span class="token punctuation">.</span><span class="token function">setUsername</span><span class="token punctuation">(</span><span class="token string">"jianjian"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        factory<span class="token punctuation">.</span><span class="token function">setPassword</span><span class="token punctuation">(</span><span class="token string">"123321"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 1.2.建立连接</span>        Connection connection <span class="token operator">=</span> factory<span class="token punctuation">.</span><span class="token function">newConnection</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 2.创建通道Channel</span>        Channel channel <span class="token operator">=</span> connection<span class="token punctuation">.</span><span class="token function">createChannel</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 3.创建队列</span>        String queueName <span class="token operator">=</span> <span class="token string">"simple.queue"</span><span class="token punctuation">;</span>        channel<span class="token punctuation">.</span><span class="token function">queueDeclare</span><span class="token punctuation">(</span>queueName<span class="token punctuation">,</span> <span class="token boolean">false</span><span class="token punctuation">,</span> <span class="token boolean">false</span><span class="token punctuation">,</span> <span class="token boolean">false</span><span class="token punctuation">,</span> null<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 4.订阅消息</span>        channel<span class="token punctuation">.</span><span class="token function">basicConsume</span><span class="token punctuation">(</span>queueName<span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name">DefaultConsumer</span><span class="token punctuation">(</span>channel<span class="token punctuation">)</span><span class="token punctuation">{</span>            <span class="token annotation punctuation">@Override</span>            <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">handleDelivery</span><span class="token punctuation">(</span>String consumerTag<span class="token punctuation">,</span> Envelope envelope<span class="token punctuation">,</span>                                       AMQP<span class="token punctuation">.</span>BasicProperties properties<span class="token punctuation">,</span> <span class="token keyword">byte</span><span class="token punctuation">[</span><span class="token punctuation">]</span> body<span class="token punctuation">)</span> <span class="token keyword">throws</span> IOException <span class="token punctuation">{</span>                <span class="token comment" spellcheck="true">// 5.处理消息</span>                String message <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">String</span><span class="token punctuation">(</span>body<span class="token punctuation">)</span><span class="token punctuation">;</span>                System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"接收到消息：【"</span> <span class="token operator">+</span> message <span class="token operator">+</span> <span class="token string">"】"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"等待接收消息。。。。"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="⓸总结"><a href="#⓸总结" class="headerlink" title="⓸总结"></a>⓸总结</h4><p>基本消息队列的消息发送流程：</p><ol><li><p>建立connection</p></li><li><p>创建channel</p></li><li><p>利用channel声明队列</p></li><li><p>利用channel向队列发送消息</p></li></ol><p>基本消息队列的消息接收流程：</p><ol><li><p>建立connection</p></li><li><p>创建channel</p></li><li><p>利用channel声明队列</p></li><li><p>定义consumer的消费行为handleDelivery()</p></li><li><p>利用channel将消费者与队列绑定</p></li></ol><h2 id="③SpringAMQP"><a href="#③SpringAMQP" class="headerlink" title="③SpringAMQP"></a>③SpringAMQP</h2><h3 id="➊简介"><a href="#➊简介" class="headerlink" title="➊简介"></a>➊简介</h3><p>AMQP（Advanced Message Queuing Protocol）是用于在应用程序之间传递业务消息的开放标准。该协议与语言和平台无关，更符合微服务中独立性的要求。</p><p>SpringAMQP 是基于AMQP协议 和 RabbitMQ 封装的一套API规范，提供了模板来发送和接收消息。包含两部分，其中spring- amqp是基础抽象，spring-rabbit是底层的默认实现。并且还利用SpringBoot对其实现了自动装配，使用起来非常方便。SpringAMQP的官方地址：<a href="https://spring.io/projects/spring-amqp">https://spring.io/projects/spring-amqp</a></p><p>SpringAMQP提供了三个功能：</p><ul><li>自动声明队列、交换机及其绑定关系</li><li>基于注解的监听器模式，异步接收消息</li><li>封装了RabbitTemplate工具，用于发送消息</li></ul><h3 id="➋Basic-Queue"><a href="#➋Basic-Queue" class="headerlink" title="➋Basic Queue"></a>➋Basic Queue</h3><p><img src="https://img.jwt1399.top/img/202210051435751.png"></p><h4 id="⓵准备工作-1"><a href="#⓵准备工作-1" class="headerlink" title="⓵准备工作"></a>⓵准备工作</h4><p>在父工程mq-demo中引入依赖</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token comment" spellcheck="true">&lt;!--AMQP依赖，包含RabbitMQ--></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework.boot<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-boot-starter-amqp<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="⓶消息发送"><a href="#⓶消息发送" class="headerlink" title="⓶消息发送"></a>⓶消息发送</h4><p>首先配置MQ地址，在publisher服务的application.yml中添加配置：</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">spring</span><span class="token punctuation">:</span>  <span class="token key atrule">rabbitmq</span><span class="token punctuation">:</span>    <span class="token key atrule">host</span><span class="token punctuation">:</span> 127.0.0.1 <span class="token comment" spellcheck="true"># 主机名</span>    <span class="token key atrule">port</span><span class="token punctuation">:</span> <span class="token number">5672 </span><span class="token comment" spellcheck="true"># 端口</span>    <span class="token key atrule">virtual-host</span><span class="token punctuation">:</span> / <span class="token comment" spellcheck="true"># 虚拟主机</span>    <span class="token key atrule">username</span><span class="token punctuation">:</span> jianjian <span class="token comment" spellcheck="true"># 用户名</span>    <span class="token key atrule">password</span><span class="token punctuation">:</span> <span class="token number">123321 </span><span class="token comment" spellcheck="true"># 密码</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>然后在publisher服务中编写测试类SpringAmqpTest，并利用RabbitTemplate实现消息发送：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@SpringBootTest</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">SpringAmqpTest</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Autowired</span>    <span class="token keyword">private</span> RabbitTemplate rabbitTemplate<span class="token punctuation">;</span>    <span class="token annotation punctuation">@Test</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">testSimpleQueue</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 队列名称</span>        String queueName <span class="token operator">=</span> <span class="token string">"simple.queue"</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 消息</span>        String message <span class="token operator">=</span> <span class="token string">"hello, spring amqp!"</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 发送消息</span>        rabbitTemplate<span class="token punctuation">.</span><span class="token function">convertAndSend</span><span class="token punctuation">(</span>queueName<span class="token punctuation">,</span> message<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="⓷消息接收"><a href="#⓷消息接收" class="headerlink" title="⓷消息接收"></a>⓷消息接收</h4><p>首先配置MQ地址，在consumer服务的application.yml中添加配置：</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">spring</span><span class="token punctuation">:</span>  <span class="token key atrule">rabbitmq</span><span class="token punctuation">:</span>    <span class="token key atrule">host</span><span class="token punctuation">:</span> 127.0.0.1 <span class="token comment" spellcheck="true"># 主机名</span>    <span class="token key atrule">port</span><span class="token punctuation">:</span> <span class="token number">5672 </span><span class="token comment" spellcheck="true"># 端口</span>    <span class="token key atrule">virtual-host</span><span class="token punctuation">:</span> / <span class="token comment" spellcheck="true"># 虚拟主机</span>    <span class="token key atrule">username</span><span class="token punctuation">:</span> jianjian <span class="token comment" spellcheck="true"># 用户名</span>    <span class="token key atrule">password</span><span class="token punctuation">:</span> <span class="token number">123321 </span><span class="token comment" spellcheck="true"># 密码</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>然后在consumer服务的<code>cn.jianjian.mq.listener</code>包中新建一个类SpringRabbitListener，代码如下：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Component</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">SpringRabbitListener</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@RabbitListener</span><span class="token punctuation">(</span>queues <span class="token operator">=</span> <span class="token string">"simple.queue"</span><span class="token punctuation">)</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">listenSimpleQueueMessage</span><span class="token punctuation">(</span>String msg<span class="token punctuation">)</span> <span class="token keyword">throws</span> InterruptedException <span class="token punctuation">{</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"spring 消费者接收到消息：【"</span> <span class="token operator">+</span> msg <span class="token operator">+</span> <span class="token string">"】"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="⓸测试"><a href="#⓸测试" class="headerlink" title="⓸测试"></a>⓸测试</h4><p>启动consumer服务，然后在publisher服务中运行测试代码，发送MQ消息</p><h3 id="➌WorkQueue"><a href="#➌WorkQueue" class="headerlink" title="➌WorkQueue"></a>➌WorkQueue</h3><p>Work queues 也被称为 Task queues。简单来说就是<strong>让多个消费者绑定到一个队列，共同消费队列中的消息</strong>。</p><p><img src="https://img.jwt1399.top/img/202210061946056.png"></p><p>当消息处理比较耗时的时候，则生产消息的速度会远远大于消息的消费速度。长此以往，消息就会堆积越来越多，无法及时处理。此时就可以使用 work 模型，多个消费者共同处理消息处理，速度就能大大提高了。</p><h4 id="⓵消息发送"><a href="#⓵消息发送" class="headerlink" title="⓵消息发送"></a>⓵消息发送</h4><p>这次我们循环发送，模拟大量消息堆积现象。在publisher服务中的SpringAmqpTest类中添加一个测试方法：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Test</span><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">testWorkQueue</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">throws</span> InterruptedException <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 队列名称</span>    String queueName <span class="token operator">=</span> <span class="token string">"simple.queue"</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 消息</span>    String message <span class="token operator">=</span> <span class="token string">"hello, message_"</span><span class="token punctuation">;</span>    <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> <span class="token number">50</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 发送消息</span>        rabbitTemplate<span class="token punctuation">.</span><span class="token function">convertAndSend</span><span class="token punctuation">(</span>queueName<span class="token punctuation">,</span> message <span class="token operator">+</span> i<span class="token punctuation">)</span><span class="token punctuation">;</span>        Thread<span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">20</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 避免发送太快</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="⓶消息接收"><a href="#⓶消息接收" class="headerlink" title="⓶消息接收"></a>⓶消息接收</h4><p>要模拟多个消费者绑定同一个队列，我们在consumer服务的SpringRabbitListener中添加2个新的方法：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@RabbitListener</span><span class="token punctuation">(</span>queues <span class="token operator">=</span> <span class="token string">"simple.queue"</span><span class="token punctuation">)</span><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">listenWorkQueue1</span><span class="token punctuation">(</span>String msg<span class="token punctuation">)</span> <span class="token keyword">throws</span> InterruptedException <span class="token punctuation">{</span>    System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"消费者1接收到消息：【"</span> <span class="token operator">+</span> msg <span class="token operator">+</span> <span class="token string">"】"</span> <span class="token operator">+</span> LocalTime<span class="token punctuation">.</span><span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    Thread<span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">20</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//模拟任务耗时</span><span class="token punctuation">}</span><span class="token annotation punctuation">@RabbitListener</span><span class="token punctuation">(</span>queues <span class="token operator">=</span> <span class="token string">"simple.queue"</span><span class="token punctuation">)</span><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">listenWorkQueue2</span><span class="token punctuation">(</span>String msg<span class="token punctuation">)</span> <span class="token keyword">throws</span> InterruptedException <span class="token punctuation">{</span>    System<span class="token punctuation">.</span>err<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"消费者2........接收到消息：【"</span> <span class="token operator">+</span> msg <span class="token operator">+</span> <span class="token string">"】"</span> <span class="token operator">+</span> LocalTime<span class="token punctuation">.</span><span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    Thread<span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">200</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="⓷测试"><a href="#⓷测试" class="headerlink" title="⓷测试"></a>⓷测试</h4><p>启动ConsumerApplication后，再执行publisher服务中刚刚编写的测试方法testWorkQueue。</p><p>可以看到消费者1很快完成了自己的25条消息。消费者2却在缓慢的处理自己的25条消息。</p><p>也就是说消息是平均分配给每个消费者，并没有考虑到消费者的处理能力。这样显然是有问题的。</p><h4 id="⓸消费预取限制"><a href="#⓸消费预取限制" class="headerlink" title="⓸消费预取限制"></a>⓸消费预取限制</h4><p>通过配置可以解决上述问题。修改consumer服务的application.yml文件，设置preFetch值，可以控制预取消息的上限</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">spring</span><span class="token punctuation">:</span>  <span class="token key atrule">rabbitmq</span><span class="token punctuation">:</span>    <span class="token key atrule">listener</span><span class="token punctuation">:</span>      <span class="token key atrule">simple</span><span class="token punctuation">:</span>        <span class="token key atrule">prefetch</span><span class="token punctuation">:</span> <span class="token number">1 </span><span class="token comment" spellcheck="true"># 每次只能获取一条消息，处理完成才能获取下一个消息</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="⓹总结"><a href="#⓹总结" class="headerlink" title="⓹总结"></a>⓹总结</h4><ul><li>多个消费者绑定到一个队列，同一条消息只会被一个消费者处理</li><li>通过设置prefetch来控制消费者预取的消息数量</li></ul><h3 id="➍Publish-x2F-Subscribe"><a href="#➍Publish-x2F-Subscribe" class="headerlink" title="➍Publish&#x2F;Subscribe"></a>➍Publish&#x2F;Subscribe</h3><p>发布订阅模式的区别就是允许将同一消息发送给多个消费者。实现方式是加入了exchange（交换机）。</p><p><img src="https://img.jwt1399.top/img/202210062001172.png" alt="发布订阅模型"></p><ul><li>Publisher：生产者，也就是要发送消息的程序，但是不再发送到队列中，而是发给交换机</li><li>Consumer：消费者，与以前一样，订阅队列，没有变化</li><li>Queue：消息队列也与以前一样，接收消息、缓存消息。</li><li>Exchange：交换机，一方面，接收生产者发送的消息。另一方面，知道如何处理消息，例如递交给某个特别队列、递交给所有队列、或是将消息丢弃。到底如何操作，取决于Exchange的类型。Exchange有以下3种类型：<ul><li>Fanout：广播，将消息交给所有绑定到交换机的队列</li><li>Direct：定向，把消息交给符合指定routing key 的队列</li><li>Topic：通配符，把消息交给符合routing pattern（路由模式） 的队列</li></ul></li></ul><p><strong>Exchange（交换机）只负责转发消息，不具备存储消息的能力</strong>，因此如果没有任何队列与Exchange绑定，或者没有符合路由规则的队列，那么消息会丢失！</p><h4 id="➀Fanout"><a href="#➀Fanout" class="headerlink" title="➀Fanout"></a>➀Fanout</h4><p><img src="https://img.jwt1399.top/img/202210071521150.png"></p><p>在广播模式下，消息发送流程是这样的：</p><ul><li>1）  可以有多个队列</li><li>2）  每个队列都要绑定到 exchange（交换机）</li><li>3）  生产者发送的消息，只能发送到交换机，交换机来决定要发给哪个队列，生产者无法决定</li><li>4）  交换机把消息发送给绑定过的所有队列</li><li>5）  订阅队列的消费者都能拿到消息</li></ul><p><strong>案例</strong></p><ul><li>创建一个交换机 jianjian.fanout，类型是 Fanout</li><li>创建两个队列fanout.queue1和fanout.queue2，绑定到交换机 jianjian.fanout</li></ul><h5 id="➀声明队列和交换机"><a href="#➀声明队列和交换机" class="headerlink" title="➀声明队列和交换机"></a>➀声明队列和交换机</h5><p>Spring提供了一个接口Exchange，来表示所有不同类型的交换机：</p><p><img src="https://img.jwt1399.top/img/202210071521480.png"></p><p>在consumer中创建一个类，声明队列和交换机：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Configuration</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">FanoutConfig</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">/**     * 声明交换机     * @return Fanout类型交换机     */</span>    <span class="token annotation punctuation">@Bean</span>    <span class="token keyword">public</span> FanoutExchange <span class="token function">fanoutExchange</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>        <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">FanoutExchange</span><span class="token punctuation">(</span><span class="token string">"jianjian.fanout"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">/**     * 第1个队列     */</span>    <span class="token annotation punctuation">@Bean</span>    <span class="token keyword">public</span> Queue <span class="token function">fanoutQueue1</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>        <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">Queue</span><span class="token punctuation">(</span><span class="token string">"fanout.queue1"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">/**     * 绑定队列和交换机     */</span>    <span class="token annotation punctuation">@Bean</span>    <span class="token keyword">public</span> Binding <span class="token function">bindingQueue1</span><span class="token punctuation">(</span>Queue fanoutQueue1<span class="token punctuation">,</span> FanoutExchange fanoutExchange<span class="token punctuation">)</span><span class="token punctuation">{</span>        <span class="token keyword">return</span> BindingBuilder<span class="token punctuation">.</span><span class="token function">bind</span><span class="token punctuation">(</span>fanoutQueue1<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">to</span><span class="token punctuation">(</span>fanoutExchange<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">/**     * 第2个队列     */</span>    <span class="token annotation punctuation">@Bean</span>    <span class="token keyword">public</span> Queue <span class="token function">fanoutQueue2</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>        <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">Queue</span><span class="token punctuation">(</span><span class="token string">"fanout.queue2"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">/**     * 绑定队列和交换机     */</span>    <span class="token annotation punctuation">@Bean</span>    <span class="token keyword">public</span> Binding <span class="token function">bindingQueue2</span><span class="token punctuation">(</span>Queue fanoutQueue2<span class="token punctuation">,</span> FanoutExchange fanoutExchange<span class="token punctuation">)</span><span class="token punctuation">{</span>        <span class="token keyword">return</span> BindingBuilder<span class="token punctuation">.</span><span class="token function">bind</span><span class="token punctuation">(</span>fanoutQueue2<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">to</span><span class="token punctuation">(</span>fanoutExchange<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h5 id="➁消息发送"><a href="#➁消息发送" class="headerlink" title="➁消息发送"></a>➁消息发送</h5><p>在publisher服务的SpringAmqpTest类中添加测试方法：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Test</span><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">testFanoutExchange</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 队列名称</span>    String exchangeName <span class="token operator">=</span> <span class="token string">"jianjian.fanout"</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 消息</span>    String message <span class="token operator">=</span> <span class="token string">"hello, everyone!"</span><span class="token punctuation">;</span>    rabbitTemplate<span class="token punctuation">.</span><span class="token function">convertAndSend</span><span class="token punctuation">(</span>exchangeName<span class="token punctuation">,</span> <span class="token string">""</span><span class="token punctuation">,</span> message<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h5 id="➂消息接收"><a href="#➂消息接收" class="headerlink" title="➂消息接收"></a>➂消息接收</h5><p>在consumer服务的SpringRabbitListener中添加两个方法，作为消费者：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@RabbitListener</span><span class="token punctuation">(</span>queues <span class="token operator">=</span> <span class="token string">"fanout.queue1"</span><span class="token punctuation">)</span><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">listenFanoutQueue1</span><span class="token punctuation">(</span>String msg<span class="token punctuation">)</span> <span class="token punctuation">{</span>    System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"消费者1接收到Fanout消息：【"</span> <span class="token operator">+</span> msg <span class="token operator">+</span> <span class="token string">"】"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token annotation punctuation">@RabbitListener</span><span class="token punctuation">(</span>queues <span class="token operator">=</span> <span class="token string">"fanout.queue2"</span><span class="token punctuation">)</span><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">listenFanoutQueue2</span><span class="token punctuation">(</span>String msg<span class="token punctuation">)</span> <span class="token punctuation">{</span>    System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"消费者2接收到Fanout消息：【"</span> <span class="token operator">+</span> msg <span class="token operator">+</span> <span class="token string">"】"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h5 id="➃总结"><a href="#➃总结" class="headerlink" title="➃总结"></a>➃总结</h5><p>交换机的作用是什么？</p><ul><li>接收publisher发送的消息</li><li>将消息按照规则路由到与之绑定的队列</li><li>不能缓存消息，路由失败，消息丢失</li><li>FanoutExchange会将消息路由到每个绑定的队列</li></ul><p>声明队列、交换机、绑定关系的Bean是什么？</p><ul><li>Queue</li><li>FanoutExchange</li><li>Binding</li></ul><h4 id="➁Direct"><a href="#➁Direct" class="headerlink" title="➁Direct"></a>➁Direct</h4><p>在<code>Fanout</code>模式中，一条消息，会被所有订阅的队列都消费。但是，在某些场景下，我们希望不同的消息被不同的队列消费。这时就要用到<code>Direct</code>类型的exchange。</p><p><img src="https://img.jwt1399.top/img/202210071530068.png"></p><p> 在Direct模型下：</p><ul><li>队列与交换机的绑定，不能是任意绑定了，而是要指定一个<code>RoutingKey</code>（路由key）</li><li>消息的发送方在向 exchange发送消息时，也必须指定消息的 <code>RoutingKey</code>。</li><li>exchange不再把消息交给每一个绑定的队列，而是根据消息的<code>Routing Key</code>进行判断，只有队列的<code>Routingkey</code>与消息的 <code>Routing key</code>完全一致，才会接收到消息</li></ul><p><strong>案例</strong></p><ol><li><p>利用@RabbitListener声明Exchange、Queue、RoutingKey</p></li><li><p>在consumer服务中，编写两个消费者方法，分别监听direct.queue1和direct.queue2</p></li><li><p>在publisher中编写测试方法，向jianjian. direct发送消息</p></li></ol><h5 id="➀基于注解声明队列和交换机"><a href="#➀基于注解声明队列和交换机" class="headerlink" title="➀基于注解声明队列和交换机"></a>➀基于注解声明队列和交换机</h5><p>基于@Bean的方式声明队列和交换机比较麻烦，Spring还提供了基于注解方式来声明。</p><p>在consumer的SpringRabbitListener中添加两个消费者，同时基于注解来声明队列和交换机：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@RabbitListener</span><span class="token punctuation">(</span>bindings <span class="token operator">=</span> <span class="token annotation punctuation">@QueueBinding</span><span class="token punctuation">(</span>    value <span class="token operator">=</span> <span class="token annotation punctuation">@Queue</span><span class="token punctuation">(</span>name <span class="token operator">=</span> <span class="token string">"direct.queue1"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>    exchange <span class="token operator">=</span> <span class="token annotation punctuation">@Exchange</span><span class="token punctuation">(</span>name <span class="token operator">=</span> <span class="token string">"jianjian.direct"</span><span class="token punctuation">,</span> type <span class="token operator">=</span> ExchangeTypes<span class="token punctuation">.</span>DIRECT<span class="token punctuation">)</span><span class="token punctuation">,</span>    key <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token string">"red"</span><span class="token punctuation">,</span> <span class="token string">"blue"</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">listenDirectQueue1</span><span class="token punctuation">(</span>String msg<span class="token punctuation">)</span><span class="token punctuation">{</span>    System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"消费者接收到direct.queue1的消息：【"</span> <span class="token operator">+</span> msg <span class="token operator">+</span> <span class="token string">"】"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token annotation punctuation">@RabbitListener</span><span class="token punctuation">(</span>bindings <span class="token operator">=</span> <span class="token annotation punctuation">@QueueBinding</span><span class="token punctuation">(</span>    value <span class="token operator">=</span> <span class="token annotation punctuation">@Queue</span><span class="token punctuation">(</span>name <span class="token operator">=</span> <span class="token string">"direct.queue2"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>    exchange <span class="token operator">=</span> <span class="token annotation punctuation">@Exchange</span><span class="token punctuation">(</span>name <span class="token operator">=</span> <span class="token string">"jianjian.direct"</span><span class="token punctuation">,</span> type <span class="token operator">=</span> ExchangeTypes<span class="token punctuation">.</span>DIRECT<span class="token punctuation">)</span><span class="token punctuation">,</span>    key <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token string">"red"</span><span class="token punctuation">,</span> <span class="token string">"yellow"</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">listenDirectQueue2</span><span class="token punctuation">(</span>String msg<span class="token punctuation">)</span><span class="token punctuation">{</span>    System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"消费者接收到direct.queue2的消息：【"</span> <span class="token operator">+</span> msg <span class="token operator">+</span> <span class="token string">"】"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h5 id="➁消息发送-1"><a href="#➁消息发送-1" class="headerlink" title="➁消息发送"></a>➁消息发送</h5><p>在publisher服务的SpringAmqpTest类中添加测试方法：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Test</span><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">testSendDirectExchange</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 交换机名称</span>    String exchangeName <span class="token operator">=</span> <span class="token string">"jianjian.direct"</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 消息</span>    String message <span class="token operator">=</span> <span class="token string">"红色警报！日本乱排核废水，导致海洋生物变异，惊现哥斯拉！"</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 发送消息</span>    rabbitTemplate<span class="token punctuation">.</span><span class="token function">convertAndSend</span><span class="token punctuation">(</span>exchangeName<span class="token punctuation">,</span> <span class="token string">"red"</span><span class="token punctuation">,</span> message<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h5 id="➂总结"><a href="#➂总结" class="headerlink" title="➂总结"></a>➂总结</h5><p>描述下Direct交换机与Fanout交换机的差异？</p><ul><li>Fanout交换机将消息路由给每一个与之绑定的队列</li><li>Direct交换机根据RoutingKey判断路由给哪个队列</li><li>如果多个队列具有相同的RoutingKey，则与Fanout功能类似</li></ul><p>基于@RabbitListener注解声明队列和交换机有哪些常见注解？</p><ul><li>@Queue</li><li>@Exchange</li></ul><h4 id="➂Topic"><a href="#➂Topic" class="headerlink" title="➂Topic"></a>➂Topic</h4><p><code>Topic</code>与<code>Direct</code>相比，都是可以根据<code>RoutingKey</code>把消息路由到不同的队列。只不过<code>Topic</code>类型<code>exchange</code>可以让队列在绑定<code>Routing key</code> 的时候使用通配符！</p><p><img src="https://img.jwt1399.top/img/202210071539760.png"></p><p><code>Routingkey</code> 一般都是由一个或多个单词组成，多个单词之间以“<code>.</code>”分割，例如： <code>item.insert</code></p><p> 通配符规则：</p><ul><li><code>#</code>：匹配一个或多个词</li><li><code>*</code>：匹配不多不少恰好1个词</li></ul><p>举例：</p><p><code>item.#</code>：能够匹配<code>item.spu.insert</code> 或者 <code>item.spu</code></p><p><code>item.*</code>：只能匹配<code>item.spu</code></p><p>解释：</p><ul><li>Queue1：绑定的是<code>china.#</code> ，因此凡是以 <code>china.</code>开头的<code>routing key</code> 都会被匹配到。包括china.news和china.weather</li><li>Queue2：绑定的是<code>#.news</code> ，因此凡是以 <code>.news</code>结尾的 <code>routing key</code> 都会被匹配。包括china.news和japan.news</li></ul><p><strong>案例</strong></p><ol><li><p>并利用@RabbitListener声明Exchange、Queue、RoutingKey</p></li><li><p>在consumer服务中，编写两个消费者方法，分别监听topic.queue1和topic.queue2</p></li><li><p>在publisher中编写测试方法，向jianjian. topic发送消息</p></li></ol><h5 id="➀消息发送"><a href="#➀消息发送" class="headerlink" title="➀消息发送"></a>➀消息发送</h5><p>在publisher服务的SpringAmqpTest类中添加测试方法：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Test</span><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">testSendTopicExchange</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 交换机名称</span>    String exchangeName <span class="token operator">=</span> <span class="token string">"itcast.topic"</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 消息</span>    String message <span class="token operator">=</span> <span class="token string">"喜报！孙悟空大战哥斯拉，胜!"</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 发送消息</span>    rabbitTemplate<span class="token punctuation">.</span><span class="token function">convertAndSend</span><span class="token punctuation">(</span>exchangeName<span class="token punctuation">,</span> <span class="token string">"china.news"</span><span class="token punctuation">,</span> message<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h5 id="➁消息接收"><a href="#➁消息接收" class="headerlink" title="➁消息接收"></a>➁消息接收</h5><p>在consumer服务的SpringRabbitListener中添加方法：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@RabbitListener</span><span class="token punctuation">(</span>bindings <span class="token operator">=</span> <span class="token annotation punctuation">@QueueBinding</span><span class="token punctuation">(</span>    value <span class="token operator">=</span> <span class="token annotation punctuation">@Queue</span><span class="token punctuation">(</span>name <span class="token operator">=</span> <span class="token string">"topic.queue1"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>    exchange <span class="token operator">=</span> <span class="token annotation punctuation">@Exchange</span><span class="token punctuation">(</span>name <span class="token operator">=</span> <span class="token string">"jianjian.topic"</span><span class="token punctuation">,</span> type <span class="token operator">=</span> ExchangeTypes<span class="token punctuation">.</span>TOPIC<span class="token punctuation">)</span><span class="token punctuation">,</span>    key <span class="token operator">=</span> <span class="token string">"china.#"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">listenTopicQueue1</span><span class="token punctuation">(</span>String msg<span class="token punctuation">)</span><span class="token punctuation">{</span>    System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"消费者接收到topic.queue1的消息：【"</span> <span class="token operator">+</span> msg <span class="token operator">+</span> <span class="token string">"】"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token annotation punctuation">@RabbitListener</span><span class="token punctuation">(</span>bindings <span class="token operator">=</span> <span class="token annotation punctuation">@QueueBinding</span><span class="token punctuation">(</span>    value <span class="token operator">=</span> <span class="token annotation punctuation">@Queue</span><span class="token punctuation">(</span>name <span class="token operator">=</span> <span class="token string">"topic.queue2"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>    exchange <span class="token operator">=</span> <span class="token annotation punctuation">@Exchange</span><span class="token punctuation">(</span>name <span class="token operator">=</span> <span class="token string">"jianjian.topic"</span><span class="token punctuation">,</span> type <span class="token operator">=</span> ExchangeTypes<span class="token punctuation">.</span>TOPIC<span class="token punctuation">)</span><span class="token punctuation">,</span>    key <span class="token operator">=</span> <span class="token string">"#.news"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">listenTopicQueue2</span><span class="token punctuation">(</span>String msg<span class="token punctuation">)</span><span class="token punctuation">{</span>    System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"消费者接收到topic.queue2的消息：【"</span> <span class="token operator">+</span> msg <span class="token operator">+</span> <span class="token string">"】"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h5 id="➂总结-1"><a href="#➂总结-1" class="headerlink" title="➂总结"></a>➂总结</h5><p>描述下Direct交换机与Topic交换机的差异？</p><ul><li>Topic交换机接收的消息RoutingKey必须是多个单词，以 <code>.</code> 分割</li><li>Topic交换机与队列绑定时的bindingKey可以指定通配符<ul><li><code>#</code>：代表0个或多个词</li><li><code>*</code>：代表1个词</li></ul></li></ul><h3 id="➑消息转换器"><a href="#➑消息转换器" class="headerlink" title="➑消息转换器"></a>➑消息转换器</h3><p>Spring会把你发送的消息序列化为字节发送给MQ，接收消息的时候，还会把字节反序列化为Java对象。</p><p><img src="https://img.jwt1399.top/img/202210082150457.png"></p><p>只不过，默认情况下Spring采用的序列化方式是JDK序列化。众所周知，JDK序列化存在下列问题：</p><ul><li>数据体积过大</li><li>有安全漏洞</li><li>可读性差</li></ul><h4 id="➀测试默认转换器"><a href="#➀测试默认转换器" class="headerlink" title="➀测试默认转换器"></a>➀测试默认转换器</h4><p>我们修改消息发送的代码，发送一个Map对象：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Test</span><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">testSendMap</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">throws</span> InterruptedException <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 准备消息</span>    Map<span class="token operator">&lt;</span>String<span class="token punctuation">,</span>Object<span class="token operator">></span> msg <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">HashMap</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    msg<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token string">"name"</span><span class="token punctuation">,</span> <span class="token string">"Jack"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    msg<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token string">"age"</span><span class="token punctuation">,</span> <span class="token number">21</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 发送消息</span>    rabbitTemplate<span class="token punctuation">.</span><span class="token function">convertAndSend</span><span class="token punctuation">(</span><span class="token string">"simple.queue"</span><span class="token punctuation">,</span><span class="token string">""</span><span class="token punctuation">,</span> msg<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>发送消息后查看控制台，可以看到消息很长，可读性差。</p><p><img src="https://img.jwt1399.top/img/202210082150423.png"></p><h4 id="➁配置JSON转换器"><a href="#➁配置JSON转换器" class="headerlink" title="➁配置JSON转换器"></a>➁配置JSON转换器</h4><p>显然，JDK序列化方式并不合适。我们希望消息体的体积更小、可读性更高，因此可以使用JSON方式来做序列化和反序列化。</p><p>在publisher和consumer两个服务中都引入jackson依赖：</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>com.fasterxml.jackson.dataformat<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>jackson-dataformat-xml<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>2.9.10<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>配置消息转换器，在启动类中添加一个Bean即可：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Bean</span><span class="token keyword">public</span> MessageConverter <span class="token function">jsonMessageConverter</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>    <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">Jackson2JsonMessageConverter</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><h1 id="10-elasticsearch"><a href="#10-elasticsearch" class="headerlink" title="10.elasticsearch"></a>10.elasticsearch</h1><h1 id="Sponsor❤️"><a href="#Sponsor❤️" class="headerlink" title="Sponsor❤️"></a>Sponsor❤️</h1><p>您的支持是我不断前进的动力，如果您感觉本文对您有所帮助的话，可以考虑打赏一下本文，用以维持本博客的运营费用，拒绝白嫖，从你我做起！🥰🥰🥰</p><table>  <tbody>     <tr>         <td style="text-align:center;">支付宝</td>         <td style="text-align:center;">微信</td>     </tr>   <tr>    <td style="text-align:center;" ><img width="200" src="https://jwt1399.top/medias/reward/alipay.png"></td>          <td style="text-align:center;"><img width="200" src="https://jwt1399.top/medias/reward/sponor_wechat.png"></td>       </tr></tbody></table>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;实用篇包含微服务治理(注册发现，远程调用，配置管理，网关路由)、Docker技术、异步通信、分布式缓存、分布式搜索&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;小简从 0 开始学 Java 知识之 &lt;a</summary>
        
      
    
    
    
    <category term="Spring" scheme="https://jwt1399.top/categories/Spring/"/>
    
    
    <category term="SpringCloud" scheme="https://jwt1399.top/tags/SpringCloud/"/>
    
  </entry>
  
  <entry>
    <title>SpringBoot-整合篇</title>
    <link href="https://jwt1399.top/posts/58591.html"/>
    <id>https://jwt1399.top/posts/58591.html</id>
    <published>2022-09-15T15:28:54.000Z</published>
    <updated>2024-09-07T15:42:59.944Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>整合篇包含SpringBoot对各种第三方技术的整合。例如SQL、NoSQL、缓存、消息队列、定时任务、文档操作、认证授权、消息通知等等</p></blockquote><p>小简从 0 开始学 Java 知识之 <a href="https://jwt1399.top/posts/29829.html">Java-学习路线</a> 中的《SpringBoot-整合篇》，不定期更新所学笔记，期待一年后的蜕变吧！&lt;有同样想法的小伙伴，可以联系我一起交流学习哦！&gt;</p><ul><li><input checked="" disabled="" type="checkbox"> 🚩时间安排：本篇长期更新</li><li><input checked="" disabled="" type="checkbox"> 🎯开始时间：09-15</li><li><input checked="" disabled="" type="checkbox"> 🎉结束时间：∞</li></ul><h2 id="1-整合SQL"><a href="#1-整合SQL" class="headerlink" title="1.整合SQL"></a>1.整合SQL</h2><h3 id="①MyBatis"><a href="#①MyBatis" class="headerlink" title="①MyBatis"></a>①MyBatis</h3><p><strong>步骤①</strong>：导入 MyBatis 的 starter 和对应数据库的坐标，或者创建项目时勾选要使用的技术</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.mybatis.spring.boot<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>mybatis-spring-boot-starter<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>2.2.2<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>mysql<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>mysql-connector-java<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>scope</span><span class="token punctuation">></span></span>runtime<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>scope</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤②</strong>：配置数据源相关信息，没有这个信息你连接哪个数据库都不知道</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">spring</span><span class="token punctuation">:</span>  <span class="token key atrule">datasource</span><span class="token punctuation">:</span>    <span class="token key atrule">driver-class-name</span><span class="token punctuation">:</span> com.mysql.cj.jdbc.Driver    <span class="token key atrule">url</span><span class="token punctuation">:</span> jdbc<span class="token punctuation">:</span>mysql<span class="token punctuation">:</span>//localhost<span class="token punctuation">:</span>3306/ssm_db<span class="token punctuation">?</span>serverTimezone=Asia/Shanghai    <span class="token key atrule">username</span><span class="token punctuation">:</span> root    <span class="token key atrule">password</span><span class="token punctuation">:</span> root<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>测试</strong></p><ul><li>测试表</li></ul><pre class="line-numbers language-sql"><code class="language-sql"><span class="token keyword">SET</span> NAMES utf8<span class="token punctuation">;</span><span class="token keyword">SET</span> FOREIGN_KEY_CHECKS <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">-- ----------------------------</span><span class="token comment" spellcheck="true">-- Table structure for tbl_book</span><span class="token comment" spellcheck="true">-- ----------------------------</span><span class="token keyword">DROP</span> <span class="token keyword">TABLE</span> <span class="token keyword">IF</span> <span class="token keyword">EXISTS</span> <span class="token punctuation">`</span>tbl_book<span class="token punctuation">`</span><span class="token punctuation">;</span><span class="token keyword">CREATE</span> <span class="token keyword">TABLE</span> <span class="token punctuation">`</span>tbl_book<span class="token punctuation">`</span>  <span class="token punctuation">(</span>  <span class="token punctuation">`</span>id<span class="token punctuation">`</span> <span class="token keyword">int</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">AUTO_INCREMENT</span><span class="token punctuation">,</span>  <span class="token punctuation">`</span>name<span class="token punctuation">`</span> <span class="token keyword">varchar</span><span class="token punctuation">(</span><span class="token number">50</span><span class="token punctuation">)</span> <span class="token keyword">CHARACTER SET</span> utf8 <span class="token keyword">COLLATE</span> utf8_general_ci <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span>  <span class="token punctuation">`</span><span class="token keyword">type</span><span class="token punctuation">`</span> <span class="token keyword">varchar</span><span class="token punctuation">(</span><span class="token number">100</span><span class="token punctuation">)</span> <span class="token keyword">CHARACTER SET</span> utf8 <span class="token keyword">COLLATE</span> utf8_general_ci <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span>  <span class="token punctuation">`</span>description<span class="token punctuation">`</span> <span class="token keyword">varchar</span><span class="token punctuation">(</span><span class="token number">100</span><span class="token punctuation">)</span> <span class="token keyword">CHARACTER SET</span> utf8 <span class="token keyword">COLLATE</span> utf8_general_ci <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span>  <span class="token keyword">PRIMARY</span> <span class="token keyword">KEY</span> <span class="token punctuation">(</span><span class="token punctuation">`</span>id<span class="token punctuation">`</span><span class="token punctuation">)</span> <span class="token keyword">USING</span> <span class="token keyword">BTREE</span><span class="token punctuation">)</span> <span class="token keyword">ENGINE</span> <span class="token operator">=</span> <span class="token keyword">InnoDB</span> <span class="token keyword">CHARACTER SET</span> <span class="token operator">=</span> utf8 <span class="token keyword">COLLATE</span> <span class="token operator">=</span> utf8_general_ci ROW_FORMAT <span class="token operator">=</span> Dynamic<span class="token punctuation">;</span><span class="token comment" spellcheck="true">-- ----------------------------</span><span class="token comment" spellcheck="true">-- Records of tbl_book</span><span class="token comment" spellcheck="true">-- ----------------------------</span><span class="token keyword">INSERT</span> <span class="token keyword">INTO</span> <span class="token punctuation">`</span>tbl_book<span class="token punctuation">`</span> <span class="token keyword">VALUES</span> <span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token string">'三体'</span><span class="token punctuation">,</span> <span class="token string">'科幻'</span><span class="token punctuation">,</span> <span class="token string">'大刘的巅峰之作，将中国科幻推向世界舞台。总共分为三部曲：《地球往事》、《黑暗森林》、《死神永生》。'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">INSERT</span> <span class="token keyword">INTO</span> <span class="token punctuation">`</span>tbl_book<span class="token punctuation">`</span> <span class="token keyword">VALUES</span> <span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">,</span> <span class="token string">'格林童话'</span><span class="token punctuation">,</span> <span class="token string">'童话'</span><span class="token punctuation">,</span> <span class="token string">'睡前故事。'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">INSERT</span> <span class="token keyword">INTO</span> <span class="token punctuation">`</span>tbl_book<span class="token punctuation">`</span> <span class="token keyword">VALUES</span> <span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">,</span> <span class="token string">'Spring 5设计模式'</span><span class="token punctuation">,</span> <span class="token string">'计算机理论'</span><span class="token punctuation">,</span> <span class="token string">'深入Spring源码剖析Spring源码中蕴含的10大设计模式'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">SET</span> FOREIGN_KEY_CHECKS <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>实体类</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Book</span> <span class="token punctuation">{</span>    <span class="token keyword">private</span> Integer id<span class="token punctuation">;</span>    <span class="token keyword">private</span> String type<span class="token punctuation">;</span>    <span class="token keyword">private</span> String name<span class="token punctuation">;</span>    <span class="token keyword">private</span> String description<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>映射接口（Dao）</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Mapper</span><span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">BookDao</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Select</span><span class="token punctuation">(</span><span class="token string">"select * from tbl_book where id = #{id}"</span><span class="token punctuation">)</span>    <span class="token keyword">public</span> Book <span class="token function">getById</span><span class="token punctuation">(</span>Integer id<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>测试类</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@SpringBootTest</span><span class="token keyword">class</span> <span class="token class-name">Springboot05MybatisApplicationTests</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Autowired</span>    <span class="token keyword">private</span> BookDao bookDao<span class="token punctuation">;</span>    <span class="token annotation punctuation">@Test</span>    <span class="token keyword">void</span> <span class="token function">contextLoads</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>bookDao<span class="token punctuation">.</span><span class="token function">getById</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>总结</strong></p><ol><li><p>整合操作需要勾选MyBatis技术，也就是导入MyBatis对应的starter</p></li><li><p>数据库连接相关信息转换成配置</p></li><li><p>数据库SQL映射需要添加@Mapper被容器识别到</p></li><li><p>MySQL 8.X驱动强制要求设置时区</p><ul><li>修改url，添加serverTimezone设定</li><li>修改MySQL数据库配置：修改mysql中的配置文件mysql.ini，在mysqld项中添加default-time-zone&#x3D;+8:00</li></ul></li><li><p>驱动类过时，提醒更换为com.mysql.cj.jdbc.Driver</p></li></ol><h3 id="②MyBatis-Plus"><a href="#②MyBatis-Plus" class="headerlink" title="②MyBatis-Plus"></a>②MyBatis-Plus</h3><p><strong>步骤①</strong>：导入对应的 starter 和数据库驱动</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>com.baomidou<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>mybatis-plus-boot-starter<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>3.4.3<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>mysql<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>mysql-connector-java<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>scope</span><span class="token punctuation">></span></span>runtime<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>scope</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤②</strong>：配置数据源相关信息</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">spring</span><span class="token punctuation">:</span>  <span class="token key atrule">datasource</span><span class="token punctuation">:</span>    <span class="token key atrule">driver-class-name</span><span class="token punctuation">:</span> com.mysql.cj.jdbc.Driver    <span class="token key atrule">url</span><span class="token punctuation">:</span> jdbc<span class="token punctuation">:</span>mysql<span class="token punctuation">:</span>//localhost<span class="token punctuation">:</span>3306/ssm_db    <span class="token key atrule">username</span><span class="token punctuation">:</span> root    <span class="token key atrule">password</span><span class="token punctuation">:</span> root<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤③</strong>：配置表名的通用前缀名</p><p>数据库的表名定义规则是tbl_模块名称，为了能和实体类相对应，需要设置所有表名的通用前缀名</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">mybatis-plus</span><span class="token punctuation">:</span>  <span class="token key atrule">global-config</span><span class="token punctuation">:</span>    <span class="token key atrule">db-config</span><span class="token punctuation">:</span>      <span class="token key atrule">table-prefix</span><span class="token punctuation">:</span> tbl_<span class="token comment" spellcheck="true">#设置所有表的通用前缀名称为tbl_</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤④</strong>：配置运行日志</p><p>通过配置运行日志就可以查阅执行时的SQL语句</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">mybatis-plus</span><span class="token punctuation">:</span>  <span class="token key atrule">global-config</span><span class="token punctuation">:</span>    <span class="token key atrule">db-config</span><span class="token punctuation">:</span>      <span class="token key atrule">table-prefix</span><span class="token punctuation">:</span> tbl_      <span class="token key atrule">id-type</span><span class="token punctuation">:</span> auto  <span class="token key atrule">configuration</span><span class="token punctuation">:</span>    <span class="token key atrule">log-impl</span><span class="token punctuation">:</span> org.apache.ibatis.logging.stdout.StdOutImpl <span class="token comment" spellcheck="true">#配置日志</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>测试</strong></p><ul><li>实体类</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Book</span> <span class="token punctuation">{</span>    <span class="token keyword">private</span> Integer id<span class="token punctuation">;</span>    <span class="token keyword">private</span> String type<span class="token punctuation">;</span>    <span class="token keyword">private</span> String name<span class="token punctuation">;</span>    <span class="token keyword">private</span> String description<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>映射接口（Dao）</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Mapper</span><span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">BookDao</span> <span class="token keyword">extends</span> <span class="token class-name">BaseMapper</span><span class="token operator">&lt;</span>Book<span class="token operator">></span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>核心在于Dao接口继承了一个BaseMapper的接口，这个接口中帮助开发者预定了若干个常用的API接口，简化了通用API接口的开发工作。</p><ul><li>测试类</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@SpringBootTest</span><span class="token keyword">class</span> <span class="token class-name">SpringbootApplicationTests</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Autowired</span>    <span class="token keyword">private</span> BookDao bookDao<span class="token punctuation">;</span>    <span class="token annotation punctuation">@Test</span>    <span class="token keyword">void</span> <span class="token function">contextLoads</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>bookDao<span class="token punctuation">.</span><span class="token function">selectById</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>bookDao<span class="token punctuation">.</span><span class="token function">selectList</span><span class="token punctuation">(</span>null<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>总结</strong></p><ol><li>手工添加MyBatis-Plus对应的starter</li><li>数据层接口使用BaseMapper简化开发</li><li>借助MyBatis-Plus日志可以查阅执行SQL语句</li><li>需要使用的第三方技术无法通过勾选确定时，需要手工添加坐标</li></ol><h3 id="③Durid"><a href="#③Durid" class="headerlink" title="③Durid"></a>③Durid</h3><p>前面整合 MyBatis 和 MyBatis-Plus 的时候，使用的数据源对象都是SpringBoot默认的数据源对象 HiKari，下面我们手工控制一下，自己指定了一个数据源对象 Druid。</p><p><strong>步骤①</strong>：导入对应的starter</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependencies</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>com.alibaba<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>druid-spring-boot-starter<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>1.2.6<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependencies</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤②</strong>：修改配置</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">spring</span><span class="token punctuation">:</span>  <span class="token key atrule">datasource</span><span class="token punctuation">:</span>    <span class="token key atrule">druid</span><span class="token punctuation">:</span>      <span class="token key atrule">driver-class-name</span><span class="token punctuation">:</span> com.mysql.cj.jdbc.Driver      <span class="token key atrule">url</span><span class="token punctuation">:</span> jdbc<span class="token punctuation">:</span>mysql<span class="token punctuation">:</span>//localhost<span class="token punctuation">:</span>3306/ssm_db<span class="token punctuation">?</span>serverTimezone=UTC      <span class="token key atrule">username</span><span class="token punctuation">:</span> root      <span class="token key atrule">password</span><span class="token punctuation">:</span> root      <span class="token comment" spellcheck="true">#除了这4个常规配置外，还有druid专用的其他配置。通过提示功能可以打开druid相关的配置查阅</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>总结</strong></p><ol><li>整合Druid需要导入Druid对应的starter</li><li>根据Druid提供的配置方式进行配置</li></ol><h2 id="2-整合NoSQL"><a href="#2-整合NoSQL" class="headerlink" title="2.整合NoSQL"></a>2.整合NoSQL</h2><h3 id="①Redis"><a href="#①Redis" class="headerlink" title="①Redis"></a>①Redis</h3><p><strong>步骤①</strong>：导入对应的 starter 和依赖</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework.boot<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-boot-starter-data-redis<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤②</strong>：修改配置</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">spring</span><span class="token punctuation">:</span>  <span class="token key atrule">redis</span><span class="token punctuation">:</span>    <span class="token key atrule">host</span><span class="token punctuation">:</span> 127.0.0.1 <span class="token comment" spellcheck="true">#指定redis所在的host</span>    <span class="token key atrule">port</span><span class="token punctuation">:</span> <span class="token number">6379  </span><span class="token comment" spellcheck="true">#指定redis的端口</span>    <span class="token key atrule">password</span><span class="token punctuation">:</span> <span class="token number">123456  </span><span class="token comment" spellcheck="true">#设置redis密码</span>    <span class="token key atrule">lettuce</span><span class="token punctuation">:</span>      <span class="token key atrule">pool</span><span class="token punctuation">:</span>        <span class="token key atrule">max-active</span><span class="token punctuation">:</span> <span class="token number">8 </span><span class="token comment" spellcheck="true">#最大连接数</span>        <span class="token key atrule">max-idle</span><span class="token punctuation">:</span> <span class="token number">8 </span><span class="token comment" spellcheck="true">#最大空闲数</span>        <span class="token key atrule">min-idle</span><span class="token punctuation">:</span> <span class="token number">0 </span><span class="token comment" spellcheck="true">#最小空闲数</span>        <span class="token key atrule">max-wait</span><span class="token punctuation">:</span> 100ms <span class="token comment" spellcheck="true">#连接等待时间</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤③</strong>：使用springboot整合redis的专用客户端接口操作</p><ul><li>RedisTemplate</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@SpringBootTest</span><span class="token keyword">class</span> <span class="token class-name">SpringRedisApplicationTests</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Resource</span>    <span class="token keyword">private</span> RedisTemplate redisTemplate<span class="token punctuation">;</span>    <span class="token annotation punctuation">@Test</span>    <span class="token keyword">void</span> <span class="token function">testString</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 1.通过RedisTemplate获取操作String类型的ValueOperations对象</span>        ValueOperations ops <span class="token operator">=</span> redisTemplate<span class="token punctuation">.</span><span class="token function">opsForValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 2.插入一条数据</span>        ops<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span><span class="token string">"name"</span><span class="token punctuation">,</span><span class="token string">"jianjian"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 3.获取数据，需要进行类型转换</span>        String name <span class="token operator">=</span> <span class="token punctuation">(</span>String<span class="token punctuation">)</span> ops<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">"name"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"name = "</span> <span class="token operator">+</span> name<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>StringRedisTemplate</li></ul><p>由于redis内部不提供java对象的存储格式，因此当操作的数据以对象的形式存在时，会进行转码，转换成字符串格式后进行操作。为了方便开发者使用基于字符串为数据的操作，springboot整合redis时提供了专用的API接口StringRedisTemplate，可以理解为这是RedisTemplate的一种指定数据泛型的操作API。</p><pre class="line-numbers language-JAVA"><code class="language-JAVA">@SpringBootTestpublic class StringRedisTemplateTest {    @Autowired    private StringRedisTemplate stringRedisTemplate;    @Test    void get(){        ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();        String name = ops.get("name");        System.out.println(name);    }}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>redis客户端选择</strong></p><blockquote><p>springboot整合redis技术提供了多种客户端兼容模式，默认提供的是lettuce客户端技术，也可以根据需要切换成指定客户端技术，例如jedis客户端技术，切换成jedis客户端技术操作步骤如下：</p></blockquote><p><strong>步骤①</strong>：导入jedis坐标</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>redis.clients<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>jedis<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤②</strong>：配置客户端技术类型，设置为jedis</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">spring</span><span class="token punctuation">:</span>  <span class="token key atrule">redis</span><span class="token punctuation">:</span>    <span class="token key atrule">host</span><span class="token punctuation">:</span> localhost    <span class="token key atrule">port</span><span class="token punctuation">:</span> <span class="token number">6379</span>    <span class="token key atrule">client-type</span><span class="token punctuation">:</span> jedis<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤③</strong>：根据需要设置对应的配置</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">spring</span><span class="token punctuation">:</span>  <span class="token key atrule">redis</span><span class="token punctuation">:</span>    <span class="token key atrule">host</span><span class="token punctuation">:</span> localhost    <span class="token key atrule">port</span><span class="token punctuation">:</span> <span class="token number">6379</span>    <span class="token key atrule">client-type</span><span class="token punctuation">:</span> jedis    <span class="token key atrule">lettuce</span><span class="token punctuation">:</span>      <span class="token key atrule">pool</span><span class="token punctuation">:</span>        <span class="token key atrule">max-active</span><span class="token punctuation">:</span> <span class="token number">16</span>    <span class="token key atrule">jedis</span><span class="token punctuation">:</span>      <span class="token key atrule">pool</span><span class="token punctuation">:</span>        <span class="token key atrule">max-active</span><span class="token punctuation">:</span> <span class="token number">16</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>lettcus与jedis区别</strong></p><ul><li>jedis连接Redis服务器是直连模式，当多线程模式下使用jedis会存在线程安全问题，解决方案可以通过配置连接池使每个连接专用，这样整体性能就大受影响</li><li>lettcus基于Netty框架进行与Redis服务器连接，底层设计中采用StatefulRedisConnection。 StatefulRedisConnection自身是线程安全的，可以保障并发访问安全问题，所以一个连接可以被多线程复用。当然lettcus也支持多连接实例一起工作</li></ul><h3 id="②ElasticSearch"><a href="#②ElasticSearch" class="headerlink" title="②ElasticSearch"></a>②ElasticSearch</h3><p><strong>步骤①</strong>：导入对应的依赖</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependencies</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework.boot<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-boot-starter-data-elasticsearch<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependencies</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤②</strong>：修改配置</p><pre class="line-numbers language-properties"><code class="language-properties"><span class="token attr-name">elasticsearch</span><span class="token punctuation">:</span><span class="token attr-name">    cluster-name</span><span class="token punctuation">:</span> <span class="token attr-value">my-application</span><span class="token attr-name">    cluster-nodes</span><span class="token punctuation">:</span> <span class="token attr-value">127.0.0.1:9200 </span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><h3 id="③MongoDB"><a href="#③MongoDB" class="headerlink" title="③MongoDB"></a>③MongoDB</h3><h2 id="3-整合消息队列"><a href="#3-整合消息队列" class="headerlink" title="3.整合消息队列"></a>3.整合消息队列</h2><h3 id="①kafka"><a href="#①kafka" class="headerlink" title="①kafka"></a>①kafka</h3><img src="https://img.jwt1399.top/img/202305051517810.png" style="zoom: 25%;" /><table><thead><tr><th>概念</th><th>解释</th></tr></thead><tbody><tr><td>Broker</td><td>节点，一个Broker代表是一个Kafka实例节点，多个Broker可以组成Kafka集群</td></tr><tr><td>Topic</td><td>主题，等同于消息系统中的队列(queue)，一个Topic中存在多个Partition</td></tr><tr><td>Partition</td><td>分区，构成Kafka存储结构的最小单位</td></tr><tr><td>offset</td><td>offset为消息偏移量，以Partition为单位，即使在同一个Topic中，不同Partition的offset也是重新开始计算(也就是会重复)</td></tr><tr><td>Group</td><td>消费者组，一个Group里面包含多个消费者</td></tr><tr><td>Message</td><td>消息，是队列中消息的承载体，也就是通信的基本单位，Producer可以向Topic中发送Message</td></tr></tbody></table><h4 id="❶下载并启动"><a href="#❶下载并启动" class="headerlink" title="❶下载并启动"></a>❶下载并启动</h4><ul><li>下载 kafka</li></ul><p>下载地址：<a href="https://kafka.apache.org/downloads">https://kafka.apache.org/downloads</a></p><ul><li>启动 Kafka 环境</li></ul><p>运行以下命令以正确的顺序启动所有服务：</p><pre class="line-numbers language-bash"><code class="language-bash"><span class="token comment" spellcheck="true"># Start the ZooKeeper service</span>$ bin/zookeeper-server-start.sh config/zookeeper.properties<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>打开另一个终端会话并运行：</p><pre class="line-numbers language-bash"><code class="language-bash"><span class="token comment" spellcheck="true"># Start the Kafka broker service</span>$ bin/kafka-server-start.sh config/server.properties<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>成功启动所有服务后，您将拥有一个基本的 Kafka 环境运行并可供使用。</p><h4 id="❷引入依赖并配置"><a href="#❷引入依赖并配置" class="headerlink" title="❷引入依赖并配置"></a>❷引入依赖并配置</h4><ul><li>引入依赖</li></ul><pre class="line-numbers language-xml"><code class="language-xml"><span class="token comment" spellcheck="true">&lt;!-- https://mvnrepository.com/artifact/org.springframework.kafka/spring-kafka --></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework.kafka<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-kafka<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>3.0.6<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>配置</li></ul><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">sping</span><span class="token punctuation">:</span>  <span class="token key atrule">kafka</span><span class="token punctuation">:</span>    <span class="token key atrule">bootstrap-servers</span><span class="token punctuation">:</span> localhost<span class="token punctuation">:</span><span class="token number">9092</span>    <span class="token key atrule">consumer</span><span class="token punctuation">:</span>      <span class="token key atrule">group-id</span><span class="token punctuation">:</span> my<span class="token punctuation">-</span>group <span class="token comment" spellcheck="true"># 默认的消费组ID</span>      <span class="token key atrule">auto-offset-reset</span><span class="token punctuation">:</span> earliest    <span class="token key atrule">producer</span><span class="token punctuation">:</span>      <span class="token key atrule">value-serializer</span><span class="token punctuation">:</span> org.apache.kafka.common.serialization.StringSerializer      <span class="token key atrule">key-serializer</span><span class="token punctuation">:</span> org.apache.kafka.common.serialization.StringSerializer<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="❸生产者发送消息"><a href="#❸生产者发送消息" class="headerlink" title="❸生产者发送消息"></a>❸生产者发送消息</h4><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Component</span><span class="token keyword">class</span> <span class="token class-name">KafkaProducer</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Autowired</span>    <span class="token keyword">private</span> KafkaTemplate kafkaTemplate<span class="token punctuation">;</span>      <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">sendMessage</span><span class="token punctuation">(</span>String topic<span class="token punctuation">,</span> String content<span class="token punctuation">)</span> <span class="token punctuation">{</span>        kafkaTemplate<span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span>topic<span class="token punctuation">,</span> content<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="❹消费者接收消息"><a href="#❹消费者接收消息" class="headerlink" title="❹消费者接收消息"></a>❹消费者接收消息</h4><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Component</span><span class="token keyword">class</span> <span class="token class-name">KafkaConsumer</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@KafkaListener</span><span class="token punctuation">(</span>topics <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token string">"test"</span><span class="token punctuation">}</span><span class="token punctuation">)</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">handleMessage</span><span class="token punctuation">(</span>ConsumerRecord record<span class="token punctuation">)</span> <span class="token punctuation">{</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>record<span class="token punctuation">.</span><span class="token function">value</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="❺测试"><a href="#❺测试" class="headerlink" title="❺测试"></a>❺测试</h4><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@SpringBootTest</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">KafkaTests</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Autowired</span>    <span class="token keyword">private</span> KafkaProducer kafkaProducer<span class="token punctuation">;</span>    <span class="token annotation punctuation">@Test</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">testKafka</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        kafkaProducer<span class="token punctuation">.</span><span class="token function">sendMessage</span><span class="token punctuation">(</span><span class="token string">"test"</span><span class="token punctuation">,</span> <span class="token string">"你好"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        kafkaProducer<span class="token punctuation">.</span><span class="token function">sendMessage</span><span class="token punctuation">(</span><span class="token string">"test"</span><span class="token punctuation">,</span> <span class="token string">"在吗"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">try</span> <span class="token punctuation">{</span>            Thread<span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">1000</span> <span class="token operator">*</span> <span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">InterruptedException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span>            e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="②RabbitMQ"><a href="#②RabbitMQ" class="headerlink" title="②RabbitMQ"></a>②RabbitMQ</h3><h4 id="❶安装RabbitMQ"><a href="#❶安装RabbitMQ" class="headerlink" title="❶安装RabbitMQ"></a>❶安装RabbitMQ</h4><ul><li>拉取镜像</li></ul><pre class="line-numbers language-properties"><code class="language-properties"><span class="token attr-name">docker</span> <span class="token attr-value">pull rabbitmq:3-management</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><ul><li>运行镜像</li></ul><pre class="line-numbers language-bash"><code class="language-bash">docker run \ -e RABBITMQ_DEFAULT_USER<span class="token operator">=</span>jianjian \ -e RABBITMQ_DEFAULT_PASS<span class="token operator">=</span>123321 \ --name mq \ --hostname mq1 \ -p 15672:15672 \ -p 5672:5672 \ -d rabbitmq:3-management<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>访问 <code>http://127.0.0.1:15672</code> 即可</p><h4 id="❷引入依赖并配置-1"><a href="#❷引入依赖并配置-1" class="headerlink" title="❷引入依赖并配置"></a>❷引入依赖并配置</h4><p>导入springboot整合amqp的starter，amqp协议默认实现为rabbitmq方案</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework.boot<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-boot-starter-amqp<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>配置RabbitMQ的服务器地址</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">spring</span><span class="token punctuation">:</span>  <span class="token key atrule">rabbitmq</span><span class="token punctuation">:</span>    <span class="token key atrule">host</span><span class="token punctuation">:</span> 127.0.0.1 <span class="token comment" spellcheck="true"># 主机名</span>    <span class="token key atrule">port</span><span class="token punctuation">:</span> <span class="token number">5672 </span><span class="token comment" spellcheck="true"># 端口</span>    <span class="token key atrule">virtual-host</span><span class="token punctuation">:</span> / <span class="token comment" spellcheck="true"># 虚拟主机</span>    <span class="token key atrule">username</span><span class="token punctuation">:</span> jianjian <span class="token comment" spellcheck="true"># 用户名</span>    <span class="token key atrule">password</span><span class="token punctuation">:</span> <span class="token number">123321 </span><span class="token comment" spellcheck="true"># 密码    </span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>案例</strong></p><ol><li>利用@RabbitListener声明Exchange、Queue、RoutingKey</li><li>在consumer服务中，编写两个消费者方法，分别监听direct.queue1和direct.queue2</li><li>在publisher中编写测试方法，向jianjian. direct发送消息</li></ol><h4 id="❸消息接收-direct"><a href="#❸消息接收-direct" class="headerlink" title="❸消息接收(direct)"></a>❸消息接收(direct)</h4><p>在consumer的SpringRabbitListener中添加两个消费者，同时基于注解来声明队列和交换机：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Component</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">SpringRabbitListener</span> <span class="token punctuation">{</span>      <span class="token annotation punctuation">@RabbitListener</span><span class="token punctuation">(</span>bindings <span class="token operator">=</span> <span class="token annotation punctuation">@QueueBinding</span><span class="token punctuation">(</span>        value <span class="token operator">=</span> <span class="token annotation punctuation">@Queue</span><span class="token punctuation">(</span>name <span class="token operator">=</span> <span class="token string">"direct.queue1"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>        exchange <span class="token operator">=</span> <span class="token annotation punctuation">@Exchange</span><span class="token punctuation">(</span>name <span class="token operator">=</span> <span class="token string">"jianjian.direct"</span><span class="token punctuation">,</span> type <span class="token operator">=</span> ExchangeTypes<span class="token punctuation">.</span>DIRECT<span class="token punctuation">)</span><span class="token punctuation">,</span>        key <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token string">"red"</span><span class="token punctuation">,</span> <span class="token string">"blue"</span><span class="token punctuation">}</span>    <span class="token punctuation">)</span><span class="token punctuation">)</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">listenDirectQueue1</span><span class="token punctuation">(</span>String msg<span class="token punctuation">)</span><span class="token punctuation">{</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"消费者接收到direct.queue1的消息：【"</span> <span class="token operator">+</span> msg <span class="token operator">+</span> <span class="token string">"】"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@RabbitListener</span><span class="token punctuation">(</span>bindings <span class="token operator">=</span> <span class="token annotation punctuation">@QueueBinding</span><span class="token punctuation">(</span>        value <span class="token operator">=</span> <span class="token annotation punctuation">@Queue</span><span class="token punctuation">(</span>name <span class="token operator">=</span> <span class="token string">"direct.queue2"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>        exchange <span class="token operator">=</span> <span class="token annotation punctuation">@Exchange</span><span class="token punctuation">(</span>name <span class="token operator">=</span> <span class="token string">"jianjian.direct"</span><span class="token punctuation">,</span> type <span class="token operator">=</span> ExchangeTypes<span class="token punctuation">.</span>DIRECT<span class="token punctuation">)</span><span class="token punctuation">,</span>        key <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token string">"red"</span><span class="token punctuation">,</span> <span class="token string">"yellow"</span><span class="token punctuation">}</span>    <span class="token punctuation">)</span><span class="token punctuation">)</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">listenDirectQueue2</span><span class="token punctuation">(</span>String msg<span class="token punctuation">)</span><span class="token punctuation">{</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"消费者接收到direct.queue2的消息：【"</span> <span class="token operator">+</span> msg <span class="token operator">+</span> <span class="token string">"】"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="❹消息发送-direct"><a href="#❹消息发送-direct" class="headerlink" title="❹消息发送(direct)"></a>❹消息发送(direct)</h4><p>在publisher服务中添加测试方法：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@SpringBootTest</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">SpringAmqpTest</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Test</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">testSendDirectExchange</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 交换机名称</span>        String exchangeName <span class="token operator">=</span> <span class="token string">"jianjian.direct"</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 消息</span>        String message <span class="token operator">=</span> <span class="token string">"红色警报！日本乱排核废水，导致海洋生物变异，惊现哥斯拉！"</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 发送消息</span>        rabbitTemplate<span class="token punctuation">.</span><span class="token function">convertAndSend</span><span class="token punctuation">(</span>exchangeName<span class="token punctuation">,</span> <span class="token string">"red"</span><span class="token punctuation">,</span> message<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="③RocketMQ"><a href="#③RocketMQ" class="headerlink" title="③RocketMQ"></a>③RocketMQ</h3><h2 id="4-整合定时任务"><a href="#4-整合定时任务" class="headerlink" title="4.整合定时任务"></a>4.整合定时任务</h2><h3 id="①Quartz"><a href="#①Quartz" class="headerlink" title="①Quartz"></a>①Quartz</h3><blockquote><p>Quartz技术是一个比较成熟的定时任务框架。springboot对其进行整合后，简化了一系列的配置，将很多配置采用默认设置，这样开发阶段就简化了很多。</p><p>Quartz相关概念</p><ul><li>工作（Job）：用于定义具体执行的工作</li><li>工作明细（JobDetail）：用于描述定时工作相关的信息</li><li>触发器（Trigger）：用于描述触发工作的执行规则，通常使用cron表达式定义规则</li><li>调度器（Scheduler）：描述了工作明细与触发器的对应关系</li></ul><p>简单说就是你定时干什么事情，这就是工作；工作不可能就是一个简单的方法，还要设置一些明细信息；工作啥时候执行，设置一个触发器；工作和触发器都是独立定义的，它们两个怎么配合到一起呢？用调度器。</p></blockquote><p><strong>步骤①</strong>：导入springboot整合Quartz的starter</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework.boot<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-boot-starter-quartz<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤②</strong>：定义任务Bean，按照Quartz的开发规范制作，继承QuartzJobBean</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">MyQuartz</span> <span class="token keyword">extends</span> <span class="token class-name">QuartzJobBean</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">//工作</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">protected</span> <span class="token keyword">void</span> <span class="token function">executeInternal</span><span class="token punctuation">(</span>JobExecutionContext context<span class="token punctuation">)</span> <span class="token keyword">throws</span> JobExecutionException <span class="token punctuation">{</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"quartz task run..."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤③</strong>：创建Quartz配置类，定义工作明细（JobDetail）与触发器的（Trigger）bean</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Configuration</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">QuartzConfig</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Bean</span>    <span class="token keyword">public</span> JobDetail <span class="token function">printJobDetail</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span> <span class="token comment" spellcheck="true">//工作明细</span>        <span class="token comment" spellcheck="true">//绑定具体的工作</span>        <span class="token keyword">return</span> JobBuilder<span class="token punctuation">.</span><span class="token function">newJob</span><span class="token punctuation">(</span>MyQuartz<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">storeDurably</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@Bean</span>    <span class="token keyword">public</span> Trigger <span class="token function">printJobTrigger</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span> <span class="token comment" spellcheck="true">//触发器</span>        <span class="token comment" spellcheck="true">//调度器定义触发工作的执行规则 从0s开始每隔5s运行一次</span>        ScheduleBuilder schedBuilder <span class="token operator">=</span> CronScheduleBuilder<span class="token punctuation">.</span><span class="token function">cronSchedule</span><span class="token punctuation">(</span><span class="token string">"0/5 * * * * ?"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">//绑定对应的工作明细</span>        <span class="token keyword">return</span> TriggerBuilder<span class="token punctuation">.</span><span class="token function">newTrigger</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">forJob</span><span class="token punctuation">(</span><span class="token function">printJobDetail</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">withSchedule</span><span class="token punctuation">(</span>schedBuilder<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>工作明细</strong>中要设置对应的具体<strong>工作</strong>，使用newJob()操作传入对应的工作任务类型即可。</p><p><strong>触发器</strong>需要绑定任务，使用forJob()操作传入绑定的工作明细对象。此处可以为工作明细设置名称然后使用名称绑定，也可以直接调用对应方法绑定。</p><p>触发器中最核心的规则是执行时间，此处使用<strong>调度器</strong>定义执行时间，执行时间描述方式使用的是<code>cron表达式</code>。</p><p><strong>cron表达式详解</strong></p><p>在spring 4.x中已经不支持7个参数的cron表达式了，要求必须是6个参数。cron表达式格式如下：</p><pre class="line-numbers language-javascript"><code class="language-javascript"><span class="token punctuation">{</span>秒<span class="token punctuation">}</span> <span class="token punctuation">{</span>分<span class="token punctuation">}</span> <span class="token punctuation">{</span>时<span class="token punctuation">}</span> <span class="token punctuation">{</span>日期（具体哪天）<span class="token punctuation">}</span> <span class="token punctuation">{</span>月<span class="token punctuation">}</span> <span class="token punctuation">{</span>星期<span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><ul><li>秒：必填项，允许的值范围是0-59，支持的特殊符号包括<code>,</code> <code>-</code> <code>*</code> <code>/</code>，<ul><li><code>,</code>表示特定的某一秒才会触发任务</li><li><code>-</code>表示一段时间内会触发任务</li><li><code>*</code>表示每一秒都会触发</li><li><code>/</code>表示从哪一个时刻开始，每隔多长时间触发一次任务。</li></ul></li><li>分：必填项，允许的值范围是0-59，支持的特殊符号和秒一样，含义类推</li><li>时：必填项，允许的值范围是0-23，支持的特殊符号和秒一样，含义类推</li><li>日期：必填项，允许的值范围是1-31，支持的特殊符号相比秒多了<code>?</code>，表示与{星期}互斥，即意味着若明确指定{星期}触发，则表示{日期}无意义，以免引起冲突和混乱。</li><li>月：必填项，允许的值范围是1-12 (JAN-DEC)，支持的特殊符号与秒一样，含义类推</li><li>星期：必填项，允许值范围是1~7 (SUN-SAT)，1代表星期天（一星期的第一天），以此类推，7代表星期六，支持的符号相比秒多了<code>?</code>，表达的含义是与{日期}互斥，即意味着若明确指定{日期}触发，则表示{星期}无意义。</li></ul><p><strong>总结</strong></p><ol><li>springboot整合Quartz就是将Quartz对应的核心对象交给spring容器管理，包含两个对象，JobDetail和Trigger对象</li><li>JobDetail对象描述的是工作的执行信息，需要绑定一个QuartzJobBean类型的对象</li><li>Trigger对象定义了一个触发器，需要为其指定绑定的JobDetail是哪个，同时要设置执行周期调度器</li></ol><h3 id="②Task"><a href="#②Task" class="headerlink" title="②Task"></a>②Task</h3><blockquote><p>Spring Task是Spring 3.0自带的定时任务，可以将它看作成一个轻量级的Quartz，功能虽然没有Quartz那样强大，但是使用起来非常简单，无需增加额外的依赖，可直接上手使用。</p></blockquote><p><strong>步骤①</strong>：开启定时任务功能，在引导类上开启定时任务功能的开关，使用注解<code>@EnableScheduling</code></p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@SpringBootApplication</span><span class="token annotation punctuation">@EnableScheduling</span> <span class="token comment" spellcheck="true">//开启定时任务功能</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">SpringbootTaskApplication</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>        SpringApplication<span class="token punctuation">.</span><span class="token function">run</span><span class="token punctuation">(</span>Springboot22TaskApplication<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">,</span> args<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤②</strong>：定义Bean，在对应要定时执行的操作上方，使用注解<code>@Scheduled</code>定义执行的时间，执行时间的描述方式还是cron表达式</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Component</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">MyBean</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Scheduled</span><span class="token punctuation">(</span>cron <span class="token operator">=</span> <span class="token string">"0/1 * * * * ?"</span><span class="token punctuation">)</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">print</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"spring task run..."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//调度线程名 ssm_1</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤③</strong>：如何想对定时任务进行相关配置，可以通过配置文件进行</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">spring</span><span class="token punctuation">:</span>  <span class="token key atrule">task</span><span class="token punctuation">:</span>       <span class="token key atrule">scheduling</span><span class="token punctuation">:</span>      <span class="token key atrule">pool</span><span class="token punctuation">:</span>           <span class="token key atrule">size</span><span class="token punctuation">:</span> <span class="token number">1</span><span class="token comment" spellcheck="true"># 任务调度线程池大小 默认 1</span>      <span class="token key atrule">thread-name-prefix</span><span class="token punctuation">:</span> ssm_      <span class="token comment" spellcheck="true"># 调度线程名称前缀 默认 scheduling-      </span>        <span class="token key atrule">shutdown</span><span class="token punctuation">:</span>          <span class="token key atrule">await-termination</span><span class="token punctuation">:</span> <span class="token boolean important">false</span><span class="token comment" spellcheck="true"># 线程池关闭时等待所有任务完成</span>          <span class="token key atrule">await-termination-period</span><span class="token punctuation">:</span> 10s<span class="token comment" spellcheck="true"># 调度线程关闭前最大等待时间，确保最后一定关闭</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>总结</strong></p><ol><li><p>spring task需要使用注解@EnableScheduling开启定时任务功能</p></li><li><p>注解@Scheduled为定时执行的的任务设置执行周期，描述方式cron表达式</p></li></ol><h2 id="5-整合认证授权"><a href="#5-整合认证授权" class="headerlink" title="5.整合认证授权"></a>5.整合认证授权</h2><h3 id="①Spring-Security"><a href="#①Spring-Security" class="headerlink" title="①Spring Security"></a>①Spring Security</h3><h3 id="②Shiro"><a href="#②Shiro" class="headerlink" title="②Shiro"></a>②Shiro</h3><h3 id="③Oauth2"><a href="#③Oauth2" class="headerlink" title="③Oauth2"></a>③Oauth2</h3><h2 id="6-整合文档操作"><a href="#6-整合文档操作" class="headerlink" title="6.整合文档操作"></a>6.整合文档操作</h2><h3 id="①PDF"><a href="#①PDF" class="headerlink" title="①PDF"></a>①PDF</h3><h3 id="②Word"><a href="#②Word" class="headerlink" title="②Word"></a>②Word</h3><h3 id="③Excel"><a href="#③Excel" class="headerlink" title="③Excel"></a>③Excel</h3><h3 id="④上传下载"><a href="#④上传下载" class="headerlink" title="④上传下载"></a>④上传下载</h3><h2 id="7-整合通知"><a href="#7-整合通知" class="headerlink" title="7.整合通知"></a>7.整合通知</h2><h3 id="①邮件"><a href="#①邮件" class="headerlink" title="①邮件"></a>①邮件</h3><h3 id="②钉钉"><a href="#②钉钉" class="headerlink" title="②钉钉"></a>②钉钉</h3><h3 id="③微信"><a href="#③微信" class="headerlink" title="③微信"></a>③微信</h3><h3 id="④短信"><a href="#④短信" class="headerlink" title="④短信"></a>④短信</h3><h2 id="Sponsor❤️"><a href="#Sponsor❤️" class="headerlink" title="Sponsor❤️"></a>Sponsor❤️</h2><p>您的支持是我不断前进的动力，如果您感觉本文对您有所帮助的话，可以考虑打赏一下本文，用以维持本博客的运营费用，拒绝白嫖，从你我做起！🥰🥰🥰</p><table>  <tbody>     <tr>         <td style="text-align:center;">支付宝</td>         <td style="text-align:center;">微信</td>     </tr>   <tr>    <td style="text-align:center;" ><img width="200" src="https://jwt1399.top/medias/reward/alipay.png"></td>          <td style="text-align:center;"><img width="200" src="https://jwt1399.top/medias/reward/sponor_wechat.png"></td>       </tr></tbody></table>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;整合篇包含SpringBoot对各种第三方技术的整合。例如SQL、NoSQL、缓存、消息队列、定时任务、文档操作、认证授权、消息通知等等&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;小简从 0 开始学 Java 知识之 &lt;a</summary>
        
      
    
    
    
    <category term="Spring" scheme="https://jwt1399.top/categories/Spring/"/>
    
    
    <category term="SpringBoot" scheme="https://jwt1399.top/tags/SpringBoot/"/>
    
  </entry>
  
  <entry>
    <title>SpringBoot-原理篇</title>
    <link href="https://jwt1399.top/posts/41747.html"/>
    <id>https://jwt1399.top/posts/41747.html</id>
    <published>2022-09-15T14:55:10.000Z</published>
    <updated>2022-10-09T15:17:25.665Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>原理篇包含自动配置工作流程、自定义starter开发、springboot程序启动流程、掌握SpringBoot内部工作流程、理解SpringBoot整合第三方技术的原理</p></blockquote><p>小简从 0 开始学 Java 知识之 <a href="https://jwt1399.top/posts/29829.html">Java-学习路线</a> 中的《SpringBoot-原理篇》，不定期更新所学笔记，期待一年后的蜕变吧！&lt;有同样想法的小伙伴，可以联系我一起交流学习哦！&gt;</p><ul><li><p><input checked="" disabled="" type="checkbox"> 🚩时间安排：预计5天更新完</p></li><li><p><input checked="" disabled="" type="checkbox"> 🎯开始时间：09-15</p></li><li><p><input checked="" disabled="" type="checkbox"> 🎉结束时间：09-17</p></li><li><p><input checked="" disabled="" type="checkbox"> 🍀总结：了解了个大概，后续再深入研究</p></li><li><p>学习目标</p></li></ul><table><thead><tr><th>课程单元</th><th>学习目标</th></tr></thead><tbody><tr><td><font color="#990000"><b>原理篇</b></font></td><td>掌握SpringBoot内部工作流程<br/>理解SpringBoot整合第三方技术的原理<br/>实现自定义开发整合第三方技术的组件</td></tr></tbody></table><ul><li>前置知识</li></ul><table><thead><tr><th>课程单元</th><th>前置知识</th><th>要求</th></tr></thead><tbody><tr><td><font color="#990000"><b>原理篇</b></font></td><td>Spring</td><td>了解Spring加载bean的各种方式<br/>知道Spring容器底层工作原理，能够阅读简单的Spring底层源码</td></tr></tbody></table><h1 id="1-自动配置工作流程"><a href="#1-自动配置工作流程" class="headerlink" title="1.自动配置工作流程"></a>1.自动配置工作流程</h1><h2 id="①bean的加载方式"><a href="#①bean的加载方式" class="headerlink" title="①bean的加载方式"></a>①bean的加载方式</h2><h3 id="❶配置文件-lt-bean-gt-标签"><a href="#❶配置文件-lt-bean-gt-标签" class="headerlink" title="❶配置文件+&lt;bean/&gt;标签"></a>❶配置文件+<code>&lt;bean/&gt;</code>标签</h3><p>最初级的bean的加载方式其实可以直击spring管控bean的核心思想，就是提供类名，然后spring就可以管理了。所以第一种方式就是给出bean的类名，内部通过反射机制加载类名后创建对象，对象就是spring管控的bean。</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token prolog">&lt;?xml version="1.0" encoding="UTF-8"?></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>beans</span> <span class="token attr-name">xmlns</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>http://www.springframework.org/schema/beans<span class="token punctuation">"</span></span>       <span class="token attr-name"><span class="token namespace">xmlns:</span>xsi</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>http://www.w3.org/2001/XMLSchema-instance<span class="token punctuation">"</span></span>       <span class="token attr-name"><span class="token namespace">xsi:</span>schemaLocation</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>    <span class="token comment" spellcheck="true">&lt;!--xml方式声明自己开发的bean--></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>bean</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>cat<span class="token punctuation">"</span></span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Cat<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>bean</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>Dog<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span>    <span class="token comment" spellcheck="true">&lt;!--xml方式声明第三方开发的bean--></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>bean</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>dataSource<span class="token punctuation">"</span></span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>com.alibaba.druid.pool.DruidDataSource<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>bean</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>com.alibaba.druid.pool.DruidDataSource<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>bean</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>com.alibaba.druid.pool.DruidDataSource<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>beans</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="❷配置文件扫描-注解"><a href="#❷配置文件扫描-注解" class="headerlink" title="❷配置文件扫描+注解"></a>❷配置文件扫描+注解</h3><p>由于❶中需要将spring管控的bean全部写在xml文件中，非常不友好。现在哪一个类要受到spring管控加载成bean，就在这个类的上面加一个注解，还可以顺带起一个bean的名字（id）。这里可以使用的注解有<code>@Component</code>以及三个衍生注解<code>@Service</code>、<code>@Controller</code>、<code>@Repository</code></p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Component</span><span class="token punctuation">(</span><span class="token string">"tom"</span><span class="token punctuation">)</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Cat</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>由于我们无法在第三方提供的技术源代码中去添加上述4个注解，因此当你需要加载第三方开发的bean的时候可以使用下列方式定义注解式的bean。定义一个方法添加<code>@Bean</code>，当前方法的返回值就可以交给spring管控，记得这个方法所在的类添加<code>@Component</code>或者<code>@Configuration</code></p><pre class="line-numbers language-JAVA"><code class="language-JAVA">@Componentpublic class DbConfig {    @Bean    public DruidDataSource dataSource(){        DruidDataSource ds = new DruidDataSource();        return ds;    }}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>上面提供的仅仅是bean的声明，spring并没有感知到这些东西，像极了上课积极回答问题的你，手举的非常高，可惜老师都没有往你的方向看上一眼。可以通过下列xml配置设置spring去检查哪些包，发现定了对应注解，就将对应的类纳入spring管控范围，声明成bean。</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token prolog">&lt;?xml version="1.0" encoding="UTF-8"?></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>beans</span> <span class="token attr-name">xmlns</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>http://www.springframework.org/schema/beans<span class="token punctuation">"</span></span>       <span class="token attr-name"><span class="token namespace">xmlns:</span>context</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>http://www.springframework.org/schema/context<span class="token punctuation">"</span></span>       <span class="token attr-name"><span class="token namespace">xmlns:</span>xsi</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>http://www.w3.org/2001/XMLSchema-instance<span class="token punctuation">"</span></span>       <span class="token attr-name"><span class="token namespace">xsi:</span>schemaLocation</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>       http://www.springframework.org/schema/beans       http://www.springframework.org/schema/beans/spring-beans.xsd       http://www.springframework.org/schema/context       http://www.springframework.org/schema/context/spring-context.xsd    <span class="token punctuation">"</span></span><span class="token punctuation">></span></span>    <span class="token comment" spellcheck="true">&lt;!--指定扫描加载bean的位置--></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span><span class="token namespace">context:</span>component-scan</span> <span class="token attr-name">base-package</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>com.jianjian.bean,com.jianjian.config<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>beans</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="❸纯注解"><a href="#❸纯注解" class="headerlink" title="❸纯注解"></a>❸纯注解</h3><p>定义一个配置类并使用<code>@ComponentScan</code>替代❷中的包扫描这个动作。</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Configuration</span><span class="token annotation punctuation">@ComponentScan</span><span class="token punctuation">(</span><span class="token punctuation">{</span><span class="token string">"com.itheima.bean"</span><span class="token punctuation">,</span><span class="token string">"com.itheima.config"</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">SpringConfig</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Bean</span>    <span class="token keyword">public</span> DruidDataSource <span class="token function">dataSource</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>        DruidDataSource ds <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">DruidDataSource</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> ds<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="❹-Import注解"><a href="#❹-Import注解" class="headerlink" title="❹@Import注解"></a>❹@Import注解</h3><p>由于使用<code>@ComponentScan</code>扫描的时候范围太大了，使用@Import注解它可以只加载你需要的bean即可。只需要在注解的参数中写上加载的类对应的.class，而且加载的bean可以不用@Component修饰</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Import</span><span class="token punctuation">(</span><span class="token punctuation">{</span>Dog<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">,</span>DbConfig<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">SpringConfig</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><h3 id="❺编程形式"><a href="#❺编程形式" class="headerlink" title="❺编程形式"></a>❺编程形式</h3><p>前面介绍的加载bean的方式都是在容器启动阶段完成bean的加载，下面这种方式就比较特殊了，可以在容器初始化完成后手动加载bean。通过这种方式可以实现编程式控制bean的加载。</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">App</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>        AnnotationConfigApplicationContext ctx <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">AnnotationConfigApplicationContext</span><span class="token punctuation">(</span>SpringConfig<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">//上下文容器对象已经初始化完毕后，手工加载bean</span>        ctx<span class="token punctuation">.</span><span class="token function">register</span><span class="token punctuation">(</span>Cat<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="❻ImportSelector接口"><a href="#❻ImportSelector接口" class="headerlink" title="❻ImportSelector接口"></a>❻ImportSelector接口</h3><p>是否可以在容器初始化过程中进行控制呢？答案是必须的。实现ImportSelector接口的类可以设置加载的bean的全路径类名，记得一点，只要能编程就能判定，能判定意味着可以控制程序的运行走向，进而控制一切。</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">MyImportSelector</span> <span class="token keyword">implements</span> <span class="token class-name">ImportSelector</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> String<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token function">selectImports</span><span class="token punctuation">(</span>AnnotationMetadata metadata<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">//各种条件的判定，判定完毕后，决定是否装载指定的bean</span>        <span class="token keyword">boolean</span> flag <span class="token operator">=</span> metadata<span class="token punctuation">.</span><span class="token function">hasAnnotation</span><span class="token punctuation">(</span><span class="token string">"org.springframework.context.annotation.Configuration"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>flag<span class="token punctuation">)</span><span class="token punctuation">{</span>            <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">String</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">{</span><span class="token string">"com.itheima.bean.Dog"</span><span class="token punctuation">}</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">String</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">{</span><span class="token string">"com.itheima.bean.Cat"</span><span class="token punctuation">}</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="❼ImportBeanDefinitionRegistrar接口"><a href="#❼ImportBeanDefinitionRegistrar接口" class="headerlink" title="❼ImportBeanDefinitionRegistrar接口"></a>❼ImportBeanDefinitionRegistrar接口</h3><p>❻中提供了给定类全路径类名控制bean加载的形式，其实bean的加载不是一个简简单单的对象，spring中定义了一个叫做BeanDefinition的东西，它才是控制bean初始化加载的核心。BeanDefinition接口中给出了若干种方法，可以控制bean的相关属性。说个最简单的，创建的对象是单例还是非单例，在BeanDefinition中定义了scope属性就可以控制这个。如果你感觉❻中没有给你开放出足够的对bean的控制操作，那么可以通过定义一个类实现ImportBeanDefinitionRegistrar接口的方式定义bean，并且还可以让你对bean的初始化进行更加细粒度的控制</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">MyRegistrar</span> <span class="token keyword">implements</span> <span class="token class-name">ImportBeanDefinitionRegistrar</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">registerBeanDefinitions</span><span class="token punctuation">(</span>AnnotationMetadata metadata<span class="token punctuation">,</span> BeanDefinitionRegistry registry<span class="token punctuation">)</span> <span class="token punctuation">{</span>        BeanDefinition beanDefinition <span class="token operator">=</span>             BeanDefinitionBuilder<span class="token punctuation">.</span><span class="token function">rootBeanDefinition</span><span class="token punctuation">(</span>BookServiceImpl2<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getBeanDefinition</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        registry<span class="token punctuation">.</span><span class="token function">registerBeanDefinition</span><span class="token punctuation">(</span><span class="token string">"bookService"</span><span class="token punctuation">,</span>beanDefinition<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="❽BeanDefinitionRegistryPostProcessor接口"><a href="#❽BeanDefinitionRegistryPostProcessor接口" class="headerlink" title="❽BeanDefinitionRegistryPostProcessor接口"></a>❽BeanDefinitionRegistryPostProcessor接口</h3><p>当某种类型的bean被接二连三的使用各种方式加载后，在你对所有加载方式的加载顺序没有完全理解清晰之前，你不知道最后谁说了算。BeanDefinitionRegistryPostProcessor，全称bean定义后处理器，它在所有bean注册都折腾完后，把最后一道关，它是最后一个运行的。</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">MyPostProcessor</span> <span class="token keyword">implements</span> <span class="token class-name">BeanDefinitionRegistryPostProcessor</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">postProcessBeanDefinitionRegistry</span><span class="token punctuation">(</span>BeanDefinitionRegistry registry<span class="token punctuation">)</span> <span class="token keyword">throws</span> BeansException <span class="token punctuation">{</span>        BeanDefinition beanDefinition <span class="token operator">=</span>             BeanDefinitionBuilder<span class="token punctuation">.</span><span class="token function">rootBeanDefinition</span><span class="token punctuation">(</span>BookServiceImpl4<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getBeanDefinition</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        registry<span class="token punctuation">.</span><span class="token function">registerBeanDefinition</span><span class="token punctuation">(</span><span class="token string">"bookService"</span><span class="token punctuation">,</span>beanDefinition<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="②bean的加载控制"><a href="#②bean的加载控制" class="headerlink" title="②bean的加载控制"></a>②bean的加载控制</h2><p>企业级开发中不可能在spring容器中进行bean的饱和式加载的。合理的加载方式是用什么加载什么。所以在spring容器中，通过判断一个类的全路径名是否能够成功加载来控制bean的加载是一种常见操作。</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">MyImportSelector</span> <span class="token keyword">implements</span> <span class="token class-name">ImportSelector</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> String<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token function">selectImports</span><span class="token punctuation">(</span>AnnotationMetadata importingClassMetadata<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">try</span> <span class="token punctuation">{</span>            Class<span class="token operator">&lt;</span><span class="token operator">?</span><span class="token operator">></span> clazz <span class="token operator">=</span> Class<span class="token punctuation">.</span><span class="token function">forName</span><span class="token punctuation">(</span><span class="token string">"com.jianjian.bean.Mouse"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">if</span><span class="token punctuation">(</span>clazz <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">String</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">{</span><span class="token string">"com.jianjian.bean.Cat"</span><span class="token punctuation">}</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">ClassNotFoundException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span>                        <span class="token comment" spellcheck="true">//e.printStackTrace();</span>            <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">String</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> null<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>通过上述的分析，可以看到此类操作将成为企业级开发中的常见操作，于是springboot将把这些常用操作给我们做了一次封装。通过注解实现上面的操作。</p><ul><li><code>@ConditionalOnClass</code>注解实现了当虚拟机中加载了指定类时才加载对应的bean。</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Bean</span><span class="token annotation punctuation">@ConditionalOnClass</span><span class="token punctuation">(</span>name <span class="token operator">=</span> <span class="token string">"com.jianjian.bean.Wolf"</span><span class="token punctuation">)</span><span class="token keyword">public</span> Cat <span class="token function">tom</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>    <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">Cat</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li><code>@ConditionalOnMissingClass</code>注解实现了虚拟机中没有加载指定的类才加载对应的bean。</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Bean</span><span class="token annotation punctuation">@ConditionalOnMissingClass</span><span class="token punctuation">(</span><span class="token string">"com.jianjian.bean.Dog"</span><span class="token punctuation">)</span><span class="token keyword">public</span> Cat <span class="token function">tom</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>    <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">Cat</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>这种条件还可以做并且的逻辑关系，写2个就是2个条件都成立，写多个就是多个条件都成立。</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Bean</span><span class="token annotation punctuation">@ConditionalOnClass</span><span class="token punctuation">(</span>name <span class="token operator">=</span> <span class="token string">"com.jianjian.bean.Wolf"</span><span class="token punctuation">)</span><span class="token annotation punctuation">@ConditionalOnMissingClass</span><span class="token punctuation">(</span><span class="token string">"com.jianjian.bean.Mouse"</span><span class="token punctuation">)</span><span class="token keyword">public</span> Cat <span class="token function">tom</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>    <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">Cat</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li><code>@ConditionalOnWebApplication</code>判定当前容器环境是否是web环境。</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Bean</span><span class="token annotation punctuation">@ConditionalOnWebApplication</span><span class="token keyword">public</span> Cat <span class="token function">tom</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>    <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">Cat</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li><code>@ConditionalOnNotWebApplication</code>判定容器环境是否是非web环境。</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Bean</span><span class="token annotation punctuation">@ConditionalOnNotWebApplication</span><span class="token keyword">public</span> Cat <span class="token function">tom</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>    <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">Cat</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li><code>@ConditionalOnBean</code>判定是否加载了指定名称的bean</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Bean</span><span class="token annotation punctuation">@ConditionalOnBean</span><span class="token punctuation">(</span>name<span class="token operator">=</span><span class="token string">"jerry"</span><span class="token punctuation">)</span><span class="token keyword">public</span> Cat <span class="token function">tom</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>    <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">Cat</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>总结</strong></p><p>springboot定义了若干种控制bean加载的条件设置注解，由spring固定加载bean变成了可以根据情况选择性的加载bean</p><h2 id="③bean的依赖属性"><a href="#③bean的依赖属性" class="headerlink" title="③bean的依赖属性"></a>③bean的依赖属性</h2><p>bean的加载及加载控制已经搞完了，下面研究一下bean内部的事情。bean在运行的时候，实现对应的业务逻辑时有可能需要开发者提供一些设置值，有就是属性了。如果使用构造方法将参数固定，灵活性不足，这个时候就可以使用bean的属性配置进行灵活的配置了。</p><p><strong>步骤①：</strong>先通过yml配置文件，设置bean运行需要使用的配置信息。</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">cartoon</span><span class="token punctuation">:</span>  <span class="token key atrule">cat</span><span class="token punctuation">:</span>    <span class="token key atrule">name</span><span class="token punctuation">:</span> <span class="token string">"图多盖洛"</span>    <span class="token key atrule">age</span><span class="token punctuation">:</span> <span class="token number">5</span>  <span class="token key atrule">mouse</span><span class="token punctuation">:</span>    <span class="token key atrule">name</span><span class="token punctuation">:</span> <span class="token string">"泰菲"</span>    <span class="token key atrule">age</span><span class="token punctuation">:</span> <span class="token number">1</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤②：</strong>然后定义一个封装属性的专用类，加载配置属性，读取对应前缀相关的属性值。</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@ConfigurationProperties</span><span class="token punctuation">(</span>prefix <span class="token operator">=</span> <span class="token string">"cartoon"</span><span class="token punctuation">)</span><span class="token annotation punctuation">@Data</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">CartoonProperties</span> <span class="token punctuation">{</span>    <span class="token keyword">private</span> Cat cat<span class="token punctuation">;</span>    <span class="token keyword">private</span> Mouse mouse<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤③：</strong>最后在使用的位置注入对应的配置即可。</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@EnableConfigurationProperties</span><span class="token punctuation">(</span>CartoonProperties<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">CartoonCatAndMouse</span><span class="token punctuation">{</span>    <span class="token annotation punctuation">@Autowired</span>    <span class="token keyword">private</span> CartoonProperties cartoonProperties<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>建议在业务类上使用@EnableConfigurationProperties声明bean，这样在不使用这个类的时候，也不会无故加载专用的属性配置类CartoonProperties，减少spring管控的资源数量。</p><h2 id="④自动装配原理"><a href="#④自动装配原理" class="headerlink" title="④自动装配原理"></a>④自动装配原理</h2><p>啥叫自动配置呢？简单说就是springboot根据我们开发者的行为猜测你要做什么事情，然后把你要用的bean都给你准备好。springboot咋做到的呢？就是看你导入了什么类，就知道你想干什么了。然后把你有可能要用的bean都给你加载好，你直接使用就行了，springboot把所需要的一切工作都做完了。整体过程分为2个阶段：</p><p><strong>阶段一：准备阶段</strong></p><ol><li><p>springboot的开发人员先大量收集Spring开发者的编程习惯，整理开发过程每一个程序经常使用的技术列表，形成一个<strong>技术集A</strong></p></li><li><p>收集<strong>技术集A</strong>的最常用的设置，把这些设置作为默认值直接设置好，得到开发过程中每一个技术的常用设置，形成每一个技术对应的<strong>设置集B</strong></p></li></ol><p><strong>阶段二：加载阶段</strong></p><ol start="3"><li>springboot初始化Spring容器基础环境，读取用户的配置信息，加载用户自定义的bean和导入的坐标，形成<strong>初始化环境</strong></li><li>springboot将<strong>技术集A</strong>包含的所有技术在SpringBoot启动时默认全部加载，这时肯定加载的东西有一些是无效的，没有用的</li><li>springboot会对<strong>技术集A</strong>中每一个技术约定启动这个技术对应的条件，并设置成按条件加载，由于开发者导入了一些bean和坐标，也就是与<strong>初始化环境</strong>，这个时候就可以根据这个<strong>初始化环境</strong>与springboot的<strong>技术集A</strong>进行比对了，哪个匹配上加载哪个</li><li>因为有些技术不做配置就无法工作，所以springboot先加载<strong>设置集B</strong>中的默认值，这样可以减少开发者配置参数的工作量</li><li>但是默认配置不一定能解决问题，于是springboot开放修改<strong>设置集B</strong>的接口，可以由开发者根据需要决定是否覆盖默认配置</li></ol><p>假定我们想自己实现自动配置的功能，都要做哪些工作呢？</p><ul><li>首先指定一个技术X，我们打算让技术X具备自动配置的功能，这个技术X可以是任意功能，这个技术隶属于上面描述的<strong>技术集A</strong></li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">CartoonCatAndMouse</span><span class="token punctuation">{</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><ul><li>然后找出技术X使用过程中的常用配置Y，这个配置隶属于上面表述的<strong>设置集B</strong></li></ul><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">cartoon</span><span class="token punctuation">:</span>  <span class="token key atrule">cat</span><span class="token punctuation">:</span>    <span class="token key atrule">name</span><span class="token punctuation">:</span> <span class="token string">"图多盖洛"</span>    <span class="token key atrule">age</span><span class="token punctuation">:</span> <span class="token number">5</span>  <span class="token key atrule">mouse</span><span class="token punctuation">:</span>    <span class="token key atrule">name</span><span class="token punctuation">:</span> <span class="token string">"泰菲"</span>    <span class="token key atrule">age</span><span class="token punctuation">:</span> <span class="token number">1</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>然后定义一个属性类封装对应的配置属性，这个过程其实就是上一节咱们做的bean的依赖属性管理，一模一样</li></ul><pre class="line-numbers language-JAVA"><code class="language-JAVA">@ConfigurationProperties(prefix = "cartoon")@Datapublic class CartoonProperties {    private Cat cat;    private Mouse mouse;}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>最后做一个配置类，当这个类加载的时候就可以初始化对应的功能bean，并且可以加载到对应的配置</li></ul><pre class="line-numbers language-JAVA"><code class="language-JAVA">@EnableConfigurationProperties(CartoonProperties.class)public class CartoonCatAndMouse implements ApplicationContextAware {    private CartoonProperties cartoonProperties;}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><ul><li>当然，你也可以为当前自动配置类设置上激活条件，例如使用@CondtionOn为其设置加载条件</li></ul><pre class="line-numbers language-JAVA"><code class="language-JAVA">@ConditionalOnClass(name="org.springframework.data.redis.core.RedisOperations")@EnableConfigurationProperties(CartoonProperties.class)public class CartoonCatAndMouse implements ApplicationContextAware {    private CartoonProperties cartoonProperties;}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>做到这里都已经做完了，但是遇到了一个全新的问题，如何让springboot启动的时候去加载这个类呢？如果不加载的话，我们做的条件判定，做的属性加载这些全部都失效了。springboot为我们开放了一个配置入口，在类路径(resources&#x2F;)下创建<code>META-INF</code>目录，并创建<code>spring.factories</code>文件，在其中添加设置，说明哪些类要启动自动配置就可以了。</p><pre class="line-numbers language-properties"><code class="language-properties"><span class="token comment" spellcheck="true"># Auto Configure</span><span class="token attr-name">org.springframework.boot.autoconfigure.EnableAutoConfiguration</span><span class="token punctuation">=</span><span class="token attr-value">\com.jianjian.bean.CartoonCatAndMouse</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>其实这个文件就做了一件事，通过这种配置的方式加载了指定的类。那自动配置的核心究竟是什么呢？自动配置其实是一个小的生态，可以按照如下思想理解：</p><ol><li>自动配置从根本上来说就是一个bean的加载</li><li>通过bean加载条件的控制给开发者一种感觉，自动配置是自适应的，可以根据情况自己判定，但实际上就是最普通的分支语句的应用，这是蒙蔽我们双眼的第一层面纱</li><li>使用bean的时候，如果不设置属性，就有默认值，如果不想用默认值，就可以自己设置，也就是可以修改部分或者全部参数，也是一种自适应的形式，其实还是需要使用分支语句来做判断的，这是蒙蔽我们双眼的第二层面纱</li><li>springboot技术提前将大量开发者有可能使用的技术提前做好了，条件也写好了，用的时候你导入了一个坐标，对应技术就可以使用了，其实就是提前帮我们把spring.factories文件写好了，这是蒙蔽我们双眼的第三层面纱</li></ol><p>​你在不知道自动配置这个知识的情况下，经过上面这一二三，你当然觉得自动配置是一种特别牛的技术，但是一窥究竟后发现，也就那么回事。而且现在springboot程序启动时，在后台偷偷的做了这么多次检测，这么多种情况判定，不用问了，效率一定是非常低的，毕竟它要检测100余种技术是否在你程序中使用。</p><p><strong>总结</strong></p><ol><li>springboot启动时先加载spring.factories文件中的org.springframework.boot.autoconfigure.EnableAutoConfiguration配置项，将其中配置的所有的类都加载成bean</li><li>在加载bean的时候，bean对应的类定义上都设置有加载条件，因此有可能加载成功，也可能条件检测失败不加载bean</li><li>对于可以正常加载成bean的类，通常会通过@EnableConfigurationProperties注解初始化对应的配置属性类并加载对应的配置</li><li>配置属性类上通常会通过@ConfigurationProperties加载指定前缀的配置，当然这些配置通常都有默认值。如果没有默认值，就强制你必须配置后使用了</li></ol><h2 id="⑤变更自动配置"><a href="#⑤变更自动配置" class="headerlink" title="⑤变更自动配置"></a>⑤变更自动配置</h2><p>springboot的自动配置并不是必然运行的，可以通过配置的形式干预是否启用对应的自动配置功能。系统默认会加载100多种自动配置的技术，我们可以先手工干预此工程，禁用一些自动配置</p><p><strong>方式一：通过yaml配置设置排除指定的自动配置类</strong></p><pre class="line-numbers language-YAML"><code class="language-YAML">spring:  autoconfigure:    exclude:      - org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p><strong>方式二：通过注解参数排除自动配置类</strong></p><pre class="line-numbers language-JAVA"><code class="language-JAVA">@EnableAutoConfiguration(excludeName = "",exclude = {})<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p><strong>方式三：排除坐标（应用面较窄）</strong></p><p>例如web程序启动时会自动启动tomcat服务器，可以通过排除坐标的方式，让加载tomcat服务器的条件失效。不过需要提醒一点，你把tomcat排除掉，记得再加一种可以运行的服务器。</p><pre class="line-numbers language-XML"><code class="language-XML"><dependencies>    <dependency>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-web</artifactId>        <!--web起步依赖环境中，排除Tomcat起步依赖，匹配自动配置条件-->        <exclusions>            <exclusion>                <groupId>org.springframework.boot</groupId>                <artifactId>spring-boot-starter-tomcat</artifactId>            </exclusion>        </exclusions>    </dependency>    <!--添加Jetty起步依赖，匹配自动配置条件-->    <dependency>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-jetty</artifactId>    </dependency></dependencies><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h1 id="2-自定义starter开发"><a href="#2-自定义starter开发" class="headerlink" title="2.自定义starter开发"></a>2.自定义starter开发</h1><p>自动配置学习完后，我们就可以基于自动配置的特性，开发springboot技术中最引以为傲的功能了，starter。其实通过前期学习，我们发现用什么技术直接导入对应的starter，然后就实现了springboot整合对应技术，再加上一些简单的配置，就可以直接使用了。这种设计方式对开发者非常友好，本章就通过一个案例的制作，开发自定义starter来实现自定义功能的快捷添加。</p><h2 id="⓪案例：网站访问次数"><a href="#⓪案例：网站访问次数" class="headerlink" title="⓪案例：网站访问次数"></a>⓪案例：网站访问次数</h2><blockquote><p>本案例的功能是统计网站独立IP访问次数的功能，并将访问信息在后台持续输出。整体功能是在后台每10秒输出一次监控信息（格式：IP+访问次数） ，当用户访问网站时，对用户的访问行为进行统计。</p><p>例如：张三访问网站功能15次，IP地址：192.168.0.135，李四访问网站功能20次，IP地址：61.129.65.248。那么在网站后台就输出如下监控信息，此信息每10秒刷新一次。</p><pre class="line-numbers language-bash"><code class="language-bash">   IP访问监控+-----ip-address-----+--num--+<span class="token operator">|</span>     192.168.0.135  <span class="token operator">|</span>   15  <span class="token operator">|</span><span class="token operator">|</span>     61.129.65.248  <span class="token operator">|</span>   20  <span class="token operator">|</span>+--------------------+-------+<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>本功能最终要实现的效果是在现有的项目中导入一个starter，对应的功能就添加上了，删除掉对应的starter，功能就消失了，要求功能要与原始项目完全解耦。因此需要开发一个独立的模块，制作对应功能。</p></blockquote><p>在进行具体制作之前，先对功能做具体的分析</p><ol><li>数据记录在什么位置</li></ol><p>最终记录的数据是一个字符串（IP地址）对应一个数字（访问次数），此处可以选择的数据存储模型可以使用java提供的map模型，也就是key-value的键值对模型，或者具有key-value键值对模型的存储技术，例如redis技术。本案例使用map作为实现方案。</p><ol start="2"><li>统计功能运行位置</li></ol><p>因为每次web请求都需要进行统计，因此使用拦截器会是比较好的方案，本案例使用拦截器来实现。不过在制作初期，先使用调用的形式进行测试，等功能完成了，再改成拦截器的实现方案。</p><ol start="3"><li>为了提升统计数据展示的灵活度，为统计功能添加配置项。</li></ol><ul><li><p>输出频度，默认10秒</p></li><li><p>数据特征：累计数据 &#x2F; 阶段数据，默认累计数据</p></li><li><p>输出格式：详细模式 &#x2F; 极简模式</p></li></ul><p>在开发中我们分成若干个步骤实现。先完成最基本的统计功能的制作，然后开发出统计报表，接下来把所有的配置都设置好，最后将拦截器功能实现，整体功能就做完了。</p><p>最终实现代码：<a href="https://jwt1399.lanzouv.com/iG0LW0buql7g">https://jwt1399.lanzouv.com/iG0LW0buql7g</a></p><p><strong>项目文件结构</strong></p><pre class="line-numbers language-bash"><code class="language-bash">ipcount-spring-boot-starter├── pom.xml└── src    └── main        ├── java        │   └── com        │       └── jianjian        │           ├── IpcountApplication.java        │           ├── autoconfigure        │           │   └── IpAutoConfiguration.java        │           ├── interceptor        │           │   ├── IpCountInterceptor.java        │           │   └── SpringMvcConfig.java        │           ├── properties        │           │   └── IpProperties.java        │           └── <span class="token function">service</span>        │               └── IpCountService.java        └── resources            ├── META-INF            │   ├── spring-configuration-metadata.json            │   └── spring.factories            └── application.yml<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="①IP计数业务功能开发"><a href="#①IP计数业务功能开发" class="headerlink" title="①IP计数业务功能开发"></a>①IP计数业务功能开发</h2><p><strong>步骤一：创建全新的模块，定义业务功能类</strong></p><p>创建web模块<code>ipcount-spring-boot-starter</code>，定义一个业务类，声明一个Map对象，用于记录ip访问次数，key是ip地址，value是访问次数</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">IpCountService</span> <span class="token punctuation">{</span>    <span class="token keyword">private</span> Map<span class="token operator">&lt;</span>String<span class="token punctuation">,</span>Integer<span class="token operator">></span> ipCountMap <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">HashMap</span><span class="token operator">&lt;</span>String<span class="token punctuation">,</span>Integer<span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>有些小伙伴可能会有疑问，不设置成静态的，如何在每次请求时进行数据共享呢？记得，当前类加载成bean以后是一个单例对象，对象都是单例的，哪里存在多个对象共享变量的问题。</p><p><strong>步骤二：制作统计功能</strong></p><p>制作统计操作对应的方法，每次访问后对应ip的记录次数+1。需要分情况处理，如果当前没有对应ip的数据，新增一条数据，否则就修改对应key的值+1即可</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">IpCountService</span> <span class="token punctuation">{</span>    <span class="token keyword">private</span> Map<span class="token operator">&lt;</span>String<span class="token punctuation">,</span>Integer<span class="token operator">></span> ipCountMap <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">HashMap</span><span class="token operator">&lt;</span>String<span class="token punctuation">,</span>Integer<span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">count</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">//每次调用当前操作，就记录当前访问的IP，然后累加访问次数</span>        <span class="token comment" spellcheck="true">//1.获取当前操作的IP地址</span>        String ip <span class="token operator">=</span> null<span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">//2.根据IP地址从Map取值，并递增</span>        Integer count <span class="token operator">=</span> ipCountMap<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>ip<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>count <span class="token operator">==</span> null<span class="token punctuation">)</span><span class="token punctuation">{</span>            ipCountMap<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span>ip<span class="token punctuation">,</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span><span class="token keyword">else</span><span class="token punctuation">{</span>            ipCountMap<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span>ip<span class="token punctuation">,</span>count <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>因为当前功能最终导入到其他项目中进行，而导入当前功能的项目是一个web项目，因此可以从容器中直接获取请求对象，获取IP地址的操作可以通过自动装配得到请求对象，然后获取对应的访问IP</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">IpCountService</span> <span class="token punctuation">{</span>    <span class="token keyword">private</span> Map<span class="token operator">&lt;</span>String<span class="token punctuation">,</span>Integer<span class="token operator">></span> ipCountMap <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">HashMap</span><span class="token operator">&lt;</span>String<span class="token punctuation">,</span>Integer<span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token annotation punctuation">@Autowired</span>    <span class="token comment" spellcheck="true">//当前的request对象的注入工作由使用当前web-starter的工程提供自动装配</span>    <span class="token keyword">private</span> HttpServletRequest httpServletRequest<span class="token punctuation">;</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">count</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">//每次调用当前操作，就记录当前访问的IP，然后累加访问次数</span>        <span class="token comment" spellcheck="true">//1.获取当前操作的IP地址</span>        String ip <span class="token operator">=</span> httpServletRequest<span class="token punctuation">.</span><span class="token function">getRemoteAddr</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">//2.根据IP地址从Map取值，并递增</span>        Integer count <span class="token operator">=</span> ipCountMap<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>ip<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>count <span class="token operator">==</span> null<span class="token punctuation">)</span><span class="token punctuation">{</span>            ipCountMap<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span>ip<span class="token punctuation">,</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span><span class="token keyword">else</span><span class="token punctuation">{</span>            ipCountMap<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span>ip<span class="token punctuation">,</span>count <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">//打印出信息</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>ipCountMap<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">//优化后代码</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">IpCountService</span> <span class="token punctuation">{</span>    <span class="token keyword">private</span> Map<span class="token operator">&lt;</span>String<span class="token punctuation">,</span> Integer<span class="token operator">></span> ipCountMap <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">HashMap</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token annotation punctuation">@Autowired</span>    <span class="token comment" spellcheck="true">//当前的request对象的注入工作由使用当前starter的工程提供自动装配</span>    <span class="token keyword">private</span> HttpServletRequest httpServletRequest<span class="token punctuation">;</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">count</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>        String ip <span class="token operator">=</span> httpServletRequest<span class="token punctuation">.</span><span class="token function">getRemoteAddr</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        ipCountMap<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span>ip<span class="token punctuation">,</span>ipCountMap<span class="token punctuation">.</span><span class="token function">getOrDefault</span><span class="token punctuation">(</span>ip<span class="token punctuation">,</span><span class="token number">0</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>ipCountMap<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤三：定义自动配置类</strong></p><p>我们需要做到的效果是导入当前模块即开启此功能，因此使用自动配置实现功能的自动装载，需要开发自动配置类在启动项目时加载当前功能。</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Configuration</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">IpAutoConfiguration</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Bean</span>    <span class="token keyword">public</span> IpCountService <span class="token function">ipCountService</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>        <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">IpCountService</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>自动配置类需要在spring.factories文件中做配置方可自动运行。</p><pre class="line-numbers language-properties"><code class="language-properties"><span class="token comment" spellcheck="true"># Auto Configure</span><span class="token attr-name">org.springframework.boot.autoconfigure.EnableAutoConfiguration</span><span class="token punctuation">=</span><span class="token attr-value">\  com.jianjian.autoconfigure.IpAutoConfiguration</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p><strong>步骤四：在其它项目中模拟调用，测试功能</strong></p><p>在其它项目调用本项目，导入当前开发的starter，切记使用之前先clean后install安装到maven仓库，确保资源更新</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>com.jianjian<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>ipcount-spring-boot-starter<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>0.0.1-SNAPSHOT<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>推荐选择调用方便的功能做测试，推荐使用分页操作，当然也可以换其他功能位置进行测试。</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@RestController</span><span class="token annotation punctuation">@RequestMapping</span><span class="token punctuation">(</span><span class="token string">"/books"</span><span class="token punctuation">)</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">BookController</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Autowired</span>    <span class="token keyword">private</span> IpCountService ipCountService<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//引入对象</span>    <span class="token annotation punctuation">@GetMapping</span><span class="token punctuation">(</span><span class="token string">"{currentPage}/{pageSize}"</span><span class="token punctuation">)</span>    <span class="token keyword">public</span> R <span class="token function">getPage</span><span class="token punctuation">(</span><span class="token annotation punctuation">@PathVariable</span> <span class="token keyword">int</span> currentPage<span class="token punctuation">,</span><span class="token annotation punctuation">@PathVariable</span> <span class="token keyword">int</span> pageSize<span class="token punctuation">,</span>Book book<span class="token punctuation">)</span><span class="token punctuation">{</span>        ipCountService<span class="token punctuation">.</span><span class="token function">count</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//测试功能</span>        IPage<span class="token operator">&lt;</span>Book<span class="token operator">></span> page <span class="token operator">=</span> bookService<span class="token punctuation">.</span><span class="token function">getPage</span><span class="token punctuation">(</span>currentPage<span class="token punctuation">,</span> pageSize<span class="token punctuation">,</span>book<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">if</span><span class="token punctuation">(</span> currentPage <span class="token operator">></span> page<span class="token punctuation">.</span><span class="token function">getPages</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">{</span>            page <span class="token operator">=</span> bookService<span class="token punctuation">.</span><span class="token function">getPage</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">)</span>page<span class="token punctuation">.</span><span class="token function">getPages</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> pageSize<span class="token punctuation">,</span>book<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">R</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">,</span> page<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>当前效果</strong>：每次调用分页操作后，可以在控制台输出当前访问的IP地址</p><p><font color="#f0f"><b>温馨提示</b></font></p><p>由于当前制作的功能需要在对应的调用位置进行坐标导入，因此必须保障仓库中具有当前开发的功能，所以每次原始代码修改后，需要重新编译并安装到仓库中。为防止问题出现，建议每次安装之前先<code>clean</code>然后<code>install</code>，保障资源进行了更新。切记切记！！</p><h2 id="②定时任务报表开发"><a href="#②定时任务报表开发" class="headerlink" title="②定时任务报表开发"></a>②定时任务报表开发</h2><p>当前已经实现了在业务功能类中记录访问数据，但是还没有定时输出监控的信息到控制台。由于监控信息需要每10秒输出1次，因此需要使用定时器功能。此处选用Spring内置的task作为实现方案。</p><p><strong>步骤一：开启定时任务功能</strong></p><p>定时任务功能开启需要在当前功能的总配置中设置，结合现有业务设定，比较合理的位置是设置在自动配置类上。加载自动配置类即启用定时任务功能。</p><pre class="line-numbers language-JAVA"><code class="language-JAVA">@EnableSchedulingpublic class IpAutoConfiguration {    @Bean    public IpCountService ipCountService(){        return new IpCountService();    }}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤二：制作显示统计数据功能</strong></p><p>定义显示统计功能的操作print()，并设置定时任务，当前设置每5秒运行一次统计数据。</p><pre class="line-numbers language-JAVA"><code class="language-JAVA">public class IpCountService {      private Map<String,Integer> ipCountMap = new HashMap<String,Integer>();    @Scheduled(cron = "0/5 * * * * ?")    public void print(){        System.out.println("         IP访问监控");        System.out.println("+-----ip-address-----+--num--+");        for (Map.Entry<String, Integer> entry : ipCountMap.entrySet()) {            String key = entry.getKey();            Integer value = entry.getValue();            System.out.println(String.format("|%18s  |%5d  |",key,value));        }        System.out.println("+--------------------+-------+");      }}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>显示样式</p><pre class="line-numbers language-bash"><code class="language-bash">         IP访问监控+-----ip-address-----+--num--+<span class="token operator">|</span>         127.0.0.1  <span class="token operator">|</span>    1  <span class="token operator">|</span>+--------------------+-------+<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>当前效果</strong>：每次调用分页操作后，可以在控制台看到统计数据，到此基础功能已经开发完毕。</p><h2 id="③使用属性设置功能参数"><a href="#③使用属性设置功能参数" class="headerlink" title="③使用属性设置功能参数"></a>③使用属性设置功能参数</h2><p>由于当前报表显示的信息格式固定，为提高报表信息显示的灵活性，需要通过yml文件设置参数，控制报表的显示格式。</p><p><strong>步骤一：定义参数格式</strong></p><p>设置3个属性，分别用来控制显示周期（cycle），阶段数据是否清空（cycleReset），数据显示格式（model）</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">tools</span><span class="token punctuation">:</span>  <span class="token key atrule">ip</span><span class="token punctuation">:</span>    <span class="token key atrule">cycle</span><span class="token punctuation">:</span> <span class="token number">10</span>    <span class="token key atrule">cycleReset</span><span class="token punctuation">:</span> <span class="token boolean important">false</span>    <span class="token key atrule">model</span><span class="token punctuation">:</span> <span class="token string">"detail"</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤二：定义封装参数的属性类，读取配置参数</strong></p><p>为防止项目组定义的参数种类过多，产生冲突，通常设置属性前缀会至少使用两级属性作为前缀进行区分。</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Data</span><span class="token annotation punctuation">@ConfigurationProperties</span><span class="token punctuation">(</span>prefix <span class="token operator">=</span> <span class="token string">"tools.ip"</span><span class="token punctuation">)</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">IpProperties</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">/**     * 日志显示周期     */</span>    <span class="token keyword">private</span> Long cycle <span class="token operator">=</span> 5L<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//默认值</span>    <span class="token comment" spellcheck="true">/**     * 是否周期内重置数据     */</span>    <span class="token keyword">private</span> Boolean cycleReset <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">/**     * 日志输出模式  detail：详细模式  simple：极简模式     */</span>    <span class="token keyword">private</span> String model <span class="token operator">=</span> LogModel<span class="token punctuation">.</span>DETAIL<span class="token punctuation">.</span>value<span class="token punctuation">;</span>    <span class="token keyword">public</span> <span class="token keyword">enum</span> LogModel<span class="token punctuation">{</span>        <span class="token function">DETAIL</span><span class="token punctuation">(</span><span class="token string">"detail"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>        <span class="token function">SIMPLE</span><span class="token punctuation">(</span><span class="token string">"simple"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">private</span> String value<span class="token punctuation">;</span>        <span class="token function">LogModel</span><span class="token punctuation">(</span>String value<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token keyword">this</span><span class="token punctuation">.</span>value <span class="token operator">=</span> value<span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">public</span> String <span class="token function">getValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token keyword">return</span> value<span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>日志输出模式是在若干个类别选项中选择某一项，对于此种分类性数据建议制作枚举定义分类数据，当然使用字符串也可以。</p><p><strong>步骤三：加载属性类</strong></p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@EnableScheduling</span><span class="token annotation punctuation">@EnableConfigurationProperties</span><span class="token punctuation">(</span>IpProperties<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">IpAutoConfiguration</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Bean</span>    <span class="token keyword">public</span> IpCountService <span class="token function">ipCountService</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>        <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">IpCountService</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤四：应用配置属性</strong></p><p>在应用配置属性的功能类中，使用自动装配加载对应的配置bean，然后使用配置信息做分支处理。</p><p>注意：清除数据的功能一定要在输出后运行，否则每次查阅的数据均为空白数据。</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">IpCountService</span> <span class="token punctuation">{</span>    <span class="token keyword">private</span> Map<span class="token operator">&lt;</span>String<span class="token punctuation">,</span>Integer<span class="token operator">></span> ipCountMap <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">HashMap</span><span class="token operator">&lt;</span>String<span class="token punctuation">,</span>Integer<span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token annotation punctuation">@Autowired</span>    <span class="token keyword">private</span> IpProperties ipProperties<span class="token punctuation">;</span>    <span class="token annotation punctuation">@Scheduled</span><span class="token punctuation">(</span>cron <span class="token operator">=</span> <span class="token string">"0/5 * * * * ?"</span><span class="token punctuation">)</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">print</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span><span class="token keyword">if</span><span class="token punctuation">(</span>ipProperties<span class="token punctuation">.</span><span class="token function">getModel</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span>IpProperties<span class="token punctuation">.</span>LogModel<span class="token punctuation">.</span>DETAIL<span class="token punctuation">.</span><span class="token function">getValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">{</span>            System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"         IP访问监控"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"+-----ip-address-----+--num--+"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">for</span> <span class="token punctuation">(</span>Map<span class="token punctuation">.</span>Entry<span class="token operator">&lt;</span>String<span class="token punctuation">,</span> Integer<span class="token operator">></span> entry <span class="token operator">:</span> ipCountMap<span class="token punctuation">.</span><span class="token function">entrySet</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                String key <span class="token operator">=</span> entry<span class="token punctuation">.</span><span class="token function">getKey</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                Integer value <span class="token operator">=</span> entry<span class="token punctuation">.</span><span class="token function">getValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>String<span class="token punctuation">.</span><span class="token function">format</span><span class="token punctuation">(</span><span class="token string">"|%18s  |%5d  |"</span><span class="token punctuation">,</span>key<span class="token punctuation">,</span>value<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>            System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"+--------------------+-------+"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span><span class="token keyword">else</span> <span class="token keyword">if</span><span class="token punctuation">(</span>ipProperties<span class="token punctuation">.</span><span class="token function">getModel</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span>IpProperties<span class="token punctuation">.</span>LogModel<span class="token punctuation">.</span>SIMPLE<span class="token punctuation">.</span><span class="token function">getValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">{</span>            System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"     IP访问监控"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"+-----ip-address-----+"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">for</span> <span class="token punctuation">(</span>String key<span class="token operator">:</span> ipCountMap<span class="token punctuation">.</span><span class="token function">keySet</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>String<span class="token punctuation">.</span><span class="token function">format</span><span class="token punctuation">(</span><span class="token string">"|%18s  |"</span><span class="token punctuation">,</span>key<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>            System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"+--------------------+"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">//阶段内统计数据归零</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>ipProperties<span class="token punctuation">.</span><span class="token function">getCycleReset</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">{</span>            ipCountMap<span class="token punctuation">.</span><span class="token function">clear</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>当前效果</strong>：在web程序端可以通过控制yml文件中的配置参数对统计信息进行格式控制。但是数据显示周期还未进行控制。</p><h2 id="④使用属性设置定时器参数"><a href="#④使用属性设置定时器参数" class="headerlink" title="④使用属性设置定时器参数"></a>④使用属性设置定时器参数</h2><p>在使用属性配置中的显示周期数据时，遇到了一些问题。由于无法在@Scheduled注解上直接使用配置数据，改用曲线救国的方针，放弃使用@EnableConfigurationProperties注解对应的功能，改成最原始的bean定义格式。</p><p><strong>步骤一：属性类定义bean并指定bean的访问名称</strong></p><p>如果此处不设置bean的访问名称，spring会使用自己的命名生成器生成bean的长名称，无法实现属性的读取</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Component</span><span class="token punctuation">(</span><span class="token string">"ipProperties"</span><span class="token punctuation">)</span><span class="token annotation punctuation">@ConfigurationProperties</span><span class="token punctuation">(</span>prefix <span class="token operator">=</span> <span class="token string">"tools.ip"</span><span class="token punctuation">)</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">IpProperties</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤二：@Scheduled注解使用<code>#&#123;&#125;</code>读取bean属性值</strong></p><p>此处读取bean名称为ipProperties的bean的cycle属性值</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Scheduled</span><span class="token punctuation">(</span>cron <span class="token operator">=</span> <span class="token string">"0/#{ipProperties.cycle} * * * * ?"</span><span class="token punctuation">)</span><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">print</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p><strong>步骤三：弃用@EnableConfigurationProperties注解对应的功能，改为导入bean的形式加载配置属性类</strong></p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@EnableScheduling</span><span class="token comment" spellcheck="true">//@EnableConfigurationProperties(IpProperties.class)</span><span class="token annotation punctuation">@Import</span><span class="token punctuation">(</span>IpProperties<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">IpAutoConfiguration</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Bean</span>    <span class="token keyword">public</span> IpCountService <span class="token function">ipCountService</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>        <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">IpCountService</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>当前效果</strong>：在web程序端可以通过控制yml文件中的配置参数对统计信息的显示周期进行控制</p><h2 id="⑤使用拦截器开发"><a href="#⑤使用拦截器开发" class="headerlink" title="⑤使用拦截器开发"></a>⑤使用拦截器开发</h2><p>目前基础功能基本上已经完成了制作，下面进行拦截器的开发。使用拦截器后使用时不需要再引入对象调用方法了，导入 starter 即可实现功能。</p><p><strong>步骤一：开发拦截器</strong></p><p>使用自动装配加载统计功能的业务类，并在拦截器中调用对应功能</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">IpCountInterceptor</span> <span class="token keyword">implements</span> <span class="token class-name">HandlerInterceptor</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Autowired</span>    <span class="token keyword">private</span> IpCountService ipCountService<span class="token punctuation">;</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> <span class="token keyword">boolean</span> <span class="token function">preHandle</span><span class="token punctuation">(</span>HttpServletRequest request<span class="token punctuation">,</span>                              HttpServletResponse response<span class="token punctuation">,</span> Object handler<span class="token punctuation">)</span> <span class="token keyword">throws</span> Exception <span class="token punctuation">{</span>        ipCountService<span class="token punctuation">.</span><span class="token function">count</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤二：配置拦截器</strong></p><p>配置mvc拦截器，设置拦截对应的请求路径。此处拦截所有请求，用户可以根据使用需要设置要拦截的请求。甚至可以在此处加载IpCountProperties中的属性，通过配置设置拦截器拦截的请求。</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Configuration</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">SpringMvcConfig</span> <span class="token keyword">implements</span> <span class="token class-name">WebMvcConfigurer</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">addInterceptors</span><span class="token punctuation">(</span>InterceptorRegistry registry<span class="token punctuation">)</span> <span class="token punctuation">{</span>        registry<span class="token punctuation">.</span><span class="token function">addInterceptor</span><span class="token punctuation">(</span><span class="token function">ipCountInterceptor</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">addPathPatterns</span><span class="token punctuation">(</span><span class="token string">"/**"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@Bean</span>    <span class="token keyword">public</span> IpCountInterceptor <span class="token function">ipCountInterceptor</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>        <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">IpCountInterceptor</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤三：去除之前的方法</strong></p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@RestController</span><span class="token annotation punctuation">@RequestMapping</span><span class="token punctuation">(</span><span class="token string">"/books"</span><span class="token punctuation">)</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">BookController</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">//@Autowired 去掉</span>    <span class="token comment" spellcheck="true">//private IpCountService ipCountService; 去掉</span>    <span class="token annotation punctuation">@GetMapping</span><span class="token punctuation">(</span><span class="token string">"{currentPage}/{pageSize}"</span><span class="token punctuation">)</span>    <span class="token keyword">public</span> R <span class="token function">getPage</span><span class="token punctuation">(</span><span class="token annotation punctuation">@PathVariable</span> <span class="token keyword">int</span> currentPage<span class="token punctuation">,</span><span class="token annotation punctuation">@PathVariable</span> <span class="token keyword">int</span> pageSize<span class="token punctuation">,</span>Book book<span class="token punctuation">)</span><span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">//ipCountService.count();  去掉</span>        IPage<span class="token operator">&lt;</span>Book<span class="token operator">></span> page <span class="token operator">=</span> bookService<span class="token punctuation">.</span><span class="token function">getPage</span><span class="token punctuation">(</span>currentPage<span class="token punctuation">,</span> pageSize<span class="token punctuation">,</span>book<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">if</span><span class="token punctuation">(</span> currentPage <span class="token operator">></span> page<span class="token punctuation">.</span><span class="token function">getPages</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">{</span>            page <span class="token operator">=</span> bookService<span class="token punctuation">.</span><span class="token function">getPage</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">)</span>page<span class="token punctuation">.</span><span class="token function">getPages</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> pageSize<span class="token punctuation">,</span>book<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">R</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">,</span> page<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>当前效果</strong>：在web程序端导入对应的starter后功能开启，去掉坐标后功能消失，实现自定义starter的效果。</p><h2 id="⑥开启yml提示功能"><a href="#⑥开启yml提示功能" class="headerlink" title="⑥开启yml提示功能"></a>⑥开启yml提示功能</h2><p>我们在使用springboot的配置属性时，都可以看到提示，导入了对应的starter后，也会有对应的提示信息出现。但是现在我们的starter没有对应的提示功能，这种设定就非常的不友好，本节解决自定义starter功能如何开启配置提示的问题。springboot提供有专用的工具实现此功能，仅需导入下列坐标。</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework.boot<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-boot-configuration-processor<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>程序编译后，在target&#x2F;META-INF目录中会生成对应的<code>json</code>提示文件，然后拷贝<code>json</code>文件到自己开发的META-INF目录中，并对其进行自定义编辑。生成的信息来自于属性类的注释信息。打开生成的<code>json</code>文件，可以看到如下信息。</p><pre class="line-numbers language-JAVA"><code class="language-JAVA">{  "groups": [    {      "name": "tools.ip",      "type": "com.jianjian.properties.IpProperties",      "sourceType": "com.jianjian.properties.IpProperties"    }  ],  "properties": [    {      "name": "tools.ip.cycle",      "type": "java.lang.Long",      "description": "日志显示周期",      "sourceType": "com.jianjian.properties.IpProperties",      "defaultValue": 5    },    {      "name": "tools.ip.cycle-reset",      "type": "java.lang.Boolean",      "description": "是否周期内重置数据",      "sourceType": "com.jianjian.properties.IpProperties",      "defaultValue": false    },    {      "name": "tools.ip.model",      "type": "java.lang.String",      "description": "日志输出模式  detail：详细模式  simple：极简模式",      "sourceType": "com.jianjian.properties.IpProperties"    }  ],  "hints": [    {      "name": "tools.ip.model",      "values": [        {          "value": "detail",          "description": "详细模式."        },        {          "value": "simple",          "description": "极简模式."        }      ]    }  ]}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li><p><code>groups属性</code>定义了当前配置的提示信息总体描述，当前配置属于哪一个属性封装类，</p></li><li><p><code>properties属性</code>描述了当前配置中每一个属性的具体设置，包含名称、类型、描述、默认值等信息。</p></li><li><p><code>hints属性</code>默认是空白的，没有进行设置。<code>hints属性</code>可以参考springboot源码中的制作，设置当前属性封装类专用的提示信息，上面为日志输出模式属性model设置了两种可选提示信息。</p></li></ul><p><strong>总结</strong></p><ol><li>自定义starter其实就是做一个独立的功能模块，核心技术是利用自动配置的效果在加载模块后加载对应的功能</li><li>通常会为自定义starter的自动配置功能添加足够的条件控制，而不会做成100%加载对功能的效果</li><li>本例中使用map保存数据，如果换用redis方案，在starter开发模块中就要导入redis对应的starter</li><li>对于配置属性务必开启提示功能，否则使用者无法感知配置应该如何书写</li></ol><p>到此当前案例全部完成，自定义stater的开发其实在第一轮开发中就已经完成了，就是创建独立模块导出独立功能，需要使用的位置导入对应的starter即可。如果是在企业中开发，记得不仅需要将开发完成的starter模块install到自己的本地仓库中，开发完毕后还要deploy到私服上，否则别人无法使用。</p><pre class="line-numbers language-bash"><code class="language-bash"><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><h1 id="3-SpringBoot程序启动"><a href="#3-SpringBoot程序启动" class="headerlink" title="3.SpringBoot程序启动"></a>3.SpringBoot程序启动</h1><h2 id="①启动过程分析"><a href="#①启动过程分析" class="headerlink" title="①启动过程分析"></a>①启动过程分析</h2><blockquote><p>不管是springboot程序还是spring程序，启动过程本质上都是在做容器的初始化，并将对应的bean初始化出来放入容器。在spring环境中，每个bean的初始化都要开发者自己添加设置，但是切换成springboot程序后，自动配置功能的添加帮助开发者提前预设了很多bean的初始化过程，加上各种各样的参数设置，使得整体初始化过程显得更简单，但是核心本质还是在做一件事，初始化容器。</p></blockquote><p>作为开发者只要搞清楚springboot提供了哪些参数设置的环节，同时初始化容器的过程中都做了哪些事情就行了。springboot初始化的参数根据参数的提供方，划分成如下3个大类，每个大类的参数又被封装了各种各样的对象，具体如下：</p><ul><li>环境属性（Environment）</li><li>系统配置（spring.factories）</li><li>参数（Arguments、application.properties）</li></ul><p>以下通过代码流向介绍了springboot程序启动时每一环节做的具体事情。</p><pre class="line-numbers language-java"><code class="language-java">Springboot30StartupApplication【<span class="token number">10</span>】<span class="token operator">-</span><span class="token operator">></span>SpringApplication<span class="token punctuation">.</span><span class="token function">run</span><span class="token punctuation">(</span>Springboot30StartupApplication<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">,</span> args<span class="token punctuation">)</span><span class="token punctuation">;</span>    SpringApplication【<span class="token number">1332</span>】<span class="token operator">-</span><span class="token operator">></span><span class="token keyword">return</span> <span class="token function">run</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Class</span><span class="token operator">&lt;</span><span class="token operator">?</span><span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">{</span> primarySource <span class="token punctuation">}</span><span class="token punctuation">,</span> args<span class="token punctuation">)</span><span class="token punctuation">;</span>        SpringApplication【<span class="token number">1343</span>】<span class="token operator">-</span><span class="token operator">></span><span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">SpringApplication</span><span class="token punctuation">(</span>primarySources<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">run</span><span class="token punctuation">(</span>args<span class="token punctuation">)</span><span class="token punctuation">;</span>            SpringApplication【<span class="token number">1343</span>】<span class="token operator">-</span><span class="token operator">></span><span class="token function">SpringApplication</span><span class="token punctuation">(</span>primarySources<span class="token punctuation">)</span>            # 加载各种配置信息，初始化各种配置对象                SpringApplication【<span class="token number">266</span>】<span class="token operator">-</span><span class="token operator">></span><span class="token keyword">this</span><span class="token punctuation">(</span>null<span class="token punctuation">,</span> primarySources<span class="token punctuation">)</span><span class="token punctuation">;</span>                    SpringApplication【<span class="token number">280</span>】<span class="token operator">-</span><span class="token operator">></span><span class="token keyword">public</span> <span class="token function">SpringApplication</span><span class="token punctuation">(</span>ResourceLoader resourceLoader<span class="token punctuation">,</span> Class<span class="token operator">&lt;</span><span class="token operator">?</span><span class="token operator">></span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span> primarySources<span class="token punctuation">)</span>                        SpringApplication【<span class="token number">281</span>】<span class="token operator">-</span><span class="token operator">></span><span class="token keyword">this</span><span class="token punctuation">.</span>resourceLoader <span class="token operator">=</span> resourceLoader<span class="token punctuation">;</span>                        # 初始化资源加载器                        SpringApplication【<span class="token number">283</span>】<span class="token operator">-</span><span class="token operator">></span><span class="token keyword">this</span><span class="token punctuation">.</span>primarySources <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">LinkedHashSet</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span>Arrays<span class="token punctuation">.</span><span class="token function">asList</span><span class="token punctuation">(</span>primarySources<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                        # 初始化配置类的类名信息（格式转换）                        SpringApplication【<span class="token number">284</span>】<span class="token operator">-</span><span class="token operator">></span><span class="token keyword">this</span><span class="token punctuation">.</span>webApplicationType <span class="token operator">=</span> WebApplicationType<span class="token punctuation">.</span><span class="token function">deduceFromClasspath</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                        # 确认当前容器加载的类型                        SpringApplication【<span class="token number">285</span>】<span class="token operator">-</span><span class="token operator">></span><span class="token keyword">this</span><span class="token punctuation">.</span>bootstrapRegistryInitializers <span class="token operator">=</span> <span class="token function">getBootstrapRegistryInitializersFromSpringFactories</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                        # 获取系统配置引导信息                        SpringApplication【<span class="token number">286</span>】<span class="token operator">-</span><span class="token operator">></span><span class="token function">setInitializers</span><span class="token punctuation">(</span><span class="token punctuation">(</span>Collection<span class="token punctuation">)</span> <span class="token function">getSpringFactoriesInstances</span><span class="token punctuation">(</span>ApplicationContextInitializer<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                        # 获取ApplicationContextInitializer<span class="token punctuation">.</span><span class="token keyword">class</span>对应的实例                        SpringApplication【<span class="token number">287</span>】<span class="token operator">-</span><span class="token operator">></span><span class="token function">setListeners</span><span class="token punctuation">(</span><span class="token punctuation">(</span>Collection<span class="token punctuation">)</span> <span class="token function">getSpringFactoriesInstances</span><span class="token punctuation">(</span>ApplicationListener<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                        # 初始化监听器，对初始化过程及运行过程进行干预                        SpringApplication【<span class="token number">288</span>】<span class="token operator">-</span><span class="token operator">></span><span class="token keyword">this</span><span class="token punctuation">.</span>mainApplicationClass <span class="token operator">=</span> <span class="token function">deduceMainApplicationClass</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                        # 初始化了引导类类名信息，备用            SpringApplication【<span class="token number">1343</span>】<span class="token operator">-</span><span class="token operator">></span><span class="token keyword">new</span> <span class="token class-name">SpringApplication</span><span class="token punctuation">(</span>primarySources<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">run</span><span class="token punctuation">(</span>args<span class="token punctuation">)</span>            # 初始化容器，得到ApplicationContext对象                SpringApplication【<span class="token number">323</span>】<span class="token operator">-</span><span class="token operator">></span>StopWatch stopWatch <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">StopWatch</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                # 设置计时器                SpringApplication【<span class="token number">324</span>】<span class="token operator">-</span><span class="token operator">></span>stopWatch<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                # 计时开始                SpringApplication【<span class="token number">325</span>】<span class="token operator">-</span><span class="token operator">></span>DefaultBootstrapContext bootstrapContext <span class="token operator">=</span> <span class="token function">createBootstrapContext</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                # 系统引导信息对应的上下文对象                SpringApplication【<span class="token number">327</span>】<span class="token operator">-</span><span class="token operator">></span><span class="token function">configureHeadlessProperty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                # 模拟输入输出信号，避免出现因缺少外设导致的信号传输失败，进而引发错误（模拟显示器，键盘，鼠标<span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>）                    java<span class="token punctuation">.</span>awt<span class="token punctuation">.</span>headless<span class="token operator">=</span><span class="token boolean">true</span>                SpringApplication【<span class="token number">328</span>】<span class="token operator">-</span><span class="token operator">></span>SpringApplicationRunListeners listeners <span class="token operator">=</span> <span class="token function">getRunListeners</span><span class="token punctuation">(</span>args<span class="token punctuation">)</span><span class="token punctuation">;</span>                # 获取当前注册的所有监听器                SpringApplication【<span class="token number">329</span>】<span class="token operator">-</span><span class="token operator">></span>listeners<span class="token punctuation">.</span><span class="token function">starting</span><span class="token punctuation">(</span>bootstrapContext<span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">.</span>mainApplicationClass<span class="token punctuation">)</span><span class="token punctuation">;</span>                # 监听器执行了对应的操作步骤                SpringApplication【<span class="token number">331</span>】<span class="token operator">-</span><span class="token operator">></span>ApplicationArguments applicationArguments <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">DefaultApplicationArguments</span><span class="token punctuation">(</span>args<span class="token punctuation">)</span><span class="token punctuation">;</span>                # 获取参数                SpringApplication【<span class="token number">333</span>】<span class="token operator">-</span><span class="token operator">></span>ConfigurableEnvironment environment <span class="token operator">=</span> <span class="token function">prepareEnvironment</span><span class="token punctuation">(</span>listeners<span class="token punctuation">,</span> bootstrapContext<span class="token punctuation">,</span> applicationArguments<span class="token punctuation">)</span><span class="token punctuation">;</span>                # 将前期读取的数据加载成了一个环境对象，用来描述信息                SpringApplication【<span class="token number">333</span>】<span class="token operator">-</span><span class="token operator">></span><span class="token function">configureIgnoreBeanInfo</span><span class="token punctuation">(</span>environment<span class="token punctuation">)</span><span class="token punctuation">;</span>                # 做了一个配置，备用                SpringApplication【<span class="token number">334</span>】<span class="token operator">-</span><span class="token operator">></span>Banner printedBanner <span class="token operator">=</span> <span class="token function">printBanner</span><span class="token punctuation">(</span>environment<span class="token punctuation">)</span><span class="token punctuation">;</span>                # 初始化logo                SpringApplication【<span class="token number">335</span>】<span class="token operator">-</span><span class="token operator">></span>context <span class="token operator">=</span> <span class="token function">createApplicationContext</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                # 创建容器对象，根据前期配置的容器类型进行判定并创建                SpringApplication【<span class="token number">363</span>】<span class="token operator">-</span><span class="token operator">></span>context<span class="token punctuation">.</span><span class="token function">setApplicationStartup</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>applicationStartup<span class="token punctuation">)</span><span class="token punctuation">;</span>                # 设置启动模式                SpringApplication【<span class="token number">337</span>】<span class="token operator">-</span><span class="token operator">></span><span class="token function">prepareContext</span><span class="token punctuation">(</span>bootstrapContext<span class="token punctuation">,</span> context<span class="token punctuation">,</span> environment<span class="token punctuation">,</span> listeners<span class="token punctuation">,</span> applicationArguments<span class="token punctuation">,</span> printedBanner<span class="token punctuation">)</span><span class="token punctuation">;</span>                # 对容器进行设置，参数来源于前期的设定                SpringApplication【<span class="token number">338</span>】<span class="token operator">-</span><span class="token operator">></span><span class="token function">refreshContext</span><span class="token punctuation">(</span>context<span class="token punctuation">)</span><span class="token punctuation">;</span>                # 刷新容器环境                SpringApplication【<span class="token number">339</span>】<span class="token operator">-</span><span class="token operator">></span><span class="token function">afterRefresh</span><span class="token punctuation">(</span>context<span class="token punctuation">,</span> applicationArguments<span class="token punctuation">)</span><span class="token punctuation">;</span>                # 刷新完毕后做后处理                SpringApplication【<span class="token number">340</span>】<span class="token operator">-</span><span class="token operator">></span>stopWatch<span class="token punctuation">.</span><span class="token function">stop</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                # 计时结束                SpringApplication【<span class="token number">341</span>】<span class="token operator">-</span><span class="token operator">></span><span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>logStartupInfo<span class="token punctuation">)</span> <span class="token punctuation">{</span>                # 判定是否记录启动时间的日志                SpringApplication【<span class="token number">342</span>】<span class="token operator">-</span><span class="token operator">></span>    <span class="token keyword">new</span> <span class="token class-name">StartupInfoLogger</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>mainApplicationClass<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">logStarted</span><span class="token punctuation">(</span><span class="token function">getApplicationLog</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> stopWatch<span class="token punctuation">)</span><span class="token punctuation">;</span>                # 创建日志对应的对象，输出日志信息，包含启动时间                SpringApplication【<span class="token number">344</span>】<span class="token operator">-</span><span class="token operator">></span>listeners<span class="token punctuation">.</span><span class="token function">started</span><span class="token punctuation">(</span>context<span class="token punctuation">)</span><span class="token punctuation">;</span>                # 监听器执行了对应的操作步骤                SpringApplication【<span class="token number">345</span>】<span class="token operator">-</span><span class="token operator">></span><span class="token function">callRunners</span><span class="token punctuation">(</span>context<span class="token punctuation">,</span> applicationArguments<span class="token punctuation">)</span><span class="token punctuation">;</span>                # 调用运行器                SpringApplication【<span class="token number">353</span>】<span class="token operator">-</span><span class="token operator">></span>listeners<span class="token punctuation">.</span><span class="token function">running</span><span class="token punctuation">(</span>context<span class="token punctuation">)</span><span class="token punctuation">;</span>                # 监听器执行了对应的操作步骤<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>上述过程描述了springboot程序启动过程中做的所有的事情。</p><h2 id="②干预启动过程"><a href="#②干预启动过程" class="headerlink" title="②干预启动过程"></a>②干预启动过程</h2><p>如果想干预springboot的启动过程，比如自定义一个数据库环境检测的程序，该如何将这个过程加入springboot的启动流程呢？遇到这样的问题，大部分技术是这样设计的，设计若干个标准接口，对应程序中的所有标准过程。当你想干预某个过程时，实现接口就行了。例如spring技术中bean的生命周期管理就是采用标准接口进行的。</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Abc</span> <span class="token keyword">implements</span> <span class="token class-name">InitializingBean</span><span class="token punctuation">,</span> DisposableBean <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">destroy</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">throws</span> Exception <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">//销毁操作</span>    <span class="token punctuation">}</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">afterPropertiesSet</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">throws</span> Exception <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">//初始化操作</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>springboot启动过程由于存在着大量的过程阶段，如果设计接口就要设计十余个标准接口，这样对开发者不友好，同时整体过程管理分散，十余个过程各自为政，管理难度大，过程过于松散。那springboot如何解决这个问题呢？它采用了一种最原始的设计模式来解决这个问题，这就是<code>监听器模式</code>，使用监听器来解决这个问题。</p><p>springboot将自身的启动过程比喻成一个大的事件，该事件是由若干个小的事件组成的。例如：</p><ul><li>org.springframework.boot.context.event.ApplicationStartingEvent<ul><li>应用启动事件，在应用运行但未进行任何处理时，将发送 ApplicationStartingEvent</li></ul></li><li>org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent<ul><li>环境准备事件，当Environment被使用，且上下文创建之前，将发送 ApplicationEnvironmentPreparedEvent</li></ul></li><li>org.springframework.boot.context.event.ApplicationContextInitializedEvent<ul><li>上下文初始化事件</li></ul></li><li>org.springframework.boot.context.event.ApplicationPreparedEvent<ul><li>应用准备事件，在开始刷新之前，bean定义被加载之后发送 ApplicationPreparedEvent</li></ul></li><li>org.springframework.context.event.ContextRefreshedEvent<ul><li>上下文刷新事件</li></ul></li><li>org.springframework.boot.context.event.ApplicationStartedEvent<ul><li>应用启动完成事件，在上下文刷新之后且所有的应用和命令行运行器被调用之前发送 ApplicationStartedEvent</li></ul></li><li>org.springframework.boot.context.event.ApplicationReadyEvent<ul><li>应用准备就绪事件，在应用程序和命令行运行器被调用之后，将发出 ApplicationReadyEvent，用于通知应用已经准备处理请求</li></ul></li><li>org.springframework.context.event.ContextClosedEvent（上下文关闭事件，对应容器关闭）</li></ul><p>上述列出的仅仅是部分事件，当应用启动后走到某一个过程点时，监听器监听到某个事件触发，就会执行对应的事件。除了系统内置的事件处理，用户就可以根据需要自定义开发当前事件触发时要做的动作。</p><pre class="line-numbers language-JAVA"><code class="language-JAVA">//设定监听器，在应用启动开始事件时进行功能追加public class MyListener implements ApplicationListener<ApplicationStartingEvent> {    public void onApplicationEvent(ApplicationStartingEvent event) {        //自定义事件处理逻辑    }}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>按照上述方案处理，用户就可以干预springboot启动过程的所有工作节点，设置自己的业务系统中独有的功能点。</p><p><strong>总结</strong></p><ol><li>springboot启动流程是先初始化容器需要的各种配置，并加载成各种对象，初始化容器时读取这些对象，创建容器</li><li>整体流程采用事件监听的机制进行过程控制，开发者可以根据需要自行扩展，添加对应的监听器绑定具体事件，就可以在事件触发位置执行开发者的业务代码</li></ol><h1 id="4-Web开发"><a href="#4-Web开发" class="headerlink" title="4.Web开发"></a>4.Web开发</h1><h2 id="1-SpringMVC自动配置概览"><a href="#1-SpringMVC自动配置概览" class="headerlink" title="1.SpringMVC自动配置概览"></a>1.SpringMVC自动配置概览</h2><ul><li><p>内容协商视图解析器和BeanName视图解析器</p></li><li><p>静态资源（包括webjars）</p></li><li><p>自动注册 <code>Converter，GenericConverter，Formatter </code></p></li><li><p>支持 <code>HttpMessageConverters</code> </p></li><li><p>自动注册 <code>MessageCodesResolver</code> （国际化）</p></li><li><p>静态index.html 页支持</p></li><li><p>自定义 <code>Favicon</code></p></li><li><p>自动使用 <code>ConfigurableWebBindingInitializer</code> （DataBinder负责将请求数据绑定到JavaBean上）</p></li></ul><h2 id="2-静态资源访问"><a href="#2-静态资源访问" class="headerlink" title="2.静态资源访问"></a>2.静态资源访问</h2><h3 id="a-静态资源目录"><a href="#a-静态资源目录" class="headerlink" title="a.静态资源目录"></a>a.静态资源目录</h3><p><strong>默认静态资源路径</strong>：  <code>/static</code> or <code>/public</code> or <code>/resources</code> or <code>/META-INF/resources</code></p><p><strong>访问 ：</strong> <code>当前项目根路径/静态资源名</code>   例如：127.0.0.1&#x2F;jianjian.png</p><p><strong>原理：</strong>当请求进来时，先去找 Controller 看能不能处理。不能处理的所有请求又都交给静态资源处理器。静态资源也找不到则响应404页面。</p><p><strong>修改默认的静态资源路径</strong>，&#x2F;static，&#x2F;public，resources，&#x2F;META-INF&#x2F;resources 将失效</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">spring</span><span class="token punctuation">:</span>    <span class="token key atrule">web</span><span class="token punctuation">:</span>        <span class="token key atrule">resources</span><span class="token punctuation">:</span>          <span class="token key atrule">static-locations</span><span class="token punctuation">:</span> <span class="token punctuation">[</span>classpath<span class="token punctuation">:</span>/jianjian/<span class="token punctuation">]</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p><strong>静态资源访问前缀</strong></p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">spring</span><span class="token punctuation">:</span>  <span class="token key atrule">mvc</span><span class="token punctuation">:</span>    <span class="token key atrule">static-path-pattern</span><span class="token punctuation">:</span> /res/**<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>访问 ：<code>当前项目根路径 + static-path-pattern + 静态资源名</code></p><p>例如：127.0.0.1&#x2F;res&#x2F;jianjian.png</p><p><strong>webjar</strong></p><p>可用jar方式添加css，js等资源文件，webjar 查找：<a href="https://www.webjars.org/%EF%BC%8C%E4%BE%8B%E5%A6%82%EF%BC%8C%E6%B7%BB%E5%8A%A0jquery">https://www.webjars.org/，例如，添加jquery</a></p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.webjars<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>jquery<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>3.5.1<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>访问：<code>localhost:8080/webjars/jquery/3.5.1/jquery.js</code> ，后面地址要按照依赖里面的包路径。</p><h3 id="b-欢迎页支持"><a href="#b-欢迎页支持" class="headerlink" title="b.欢迎页支持"></a>b.欢迎页支持</h3><ul><li>静态资源路径下 index.html<ul><li>可以配置静态资源路径</li><li>配置<strong>静态资源的访问前缀</strong>，会导致 index.html不能被默认访问</li></ul></li></ul><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">spring</span><span class="token punctuation">:</span><span class="token comment" spellcheck="true">#  mvc:</span><span class="token comment" spellcheck="true">#    static-path-pattern: /res/**   这个会导致欢迎页功能失效</span>    <span class="token key atrule">web</span><span class="token punctuation">:</span>        <span class="token key atrule">resources</span><span class="token punctuation">:</span>            <span class="token key atrule">static-locations</span><span class="token punctuation">:</span> <span class="token punctuation">[</span>classpath<span class="token punctuation">:</span>/haha/<span class="token punctuation">]</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>controller处理index</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@RequestMapping</span><span class="token punctuation">(</span><span class="token string">"/"</span><span class="token punctuation">)</span><span class="token keyword">public</span> String <span class="token function">home</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>  <span class="token keyword">return</span> <span class="token string">"index"</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><h3 id="c-自定义Favicon"><a href="#c-自定义Favicon" class="headerlink" title="c.自定义Favicon"></a>c.自定义Favicon</h3><p>将 favicon.ico 放在静态资源目录下即可，但是配置<strong>静态资源访问前缀</strong>会导致 Favicon 功能失效</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">spring</span><span class="token punctuation">:</span><span class="token comment" spellcheck="true">#  mvc:</span><span class="token comment" spellcheck="true">#    static-path-pattern: /res/**   这个会导致 Favicon 功能失效</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><h3 id="d-静态资源配置原理"><a href="#d-静态资源配置原理" class="headerlink" title="d.静态资源配置原理"></a>d.静态资源配置原理</h3><p>。。。</p><h2 id="3-请求参数处理"><a href="#3-请求参数处理" class="headerlink" title="3.请求参数处理"></a>3.请求参数处理</h2><h3 id="a-rest使用与原理"><a href="#a-rest使用与原理" class="headerlink" title="a.rest使用与原理"></a>a.rest使用与原理</h3><ul><li><p>@xxxMapping</p><ul><li>@GetMapping</li><li>@PostMapping</li><li>@PutMapping</li><li>@DeleteMapping</li></ul></li><li><p>Rest风格支持（<em>使用<strong>HTTP</strong>请求方式动词来表示对资源的操作</em>）</p><ul><li><p>以前：</p><ul><li><code>/getUser</code>  获取用户  </li><li><code>/deleteUser</code> 删除用户   </li><li><code>/editUser</code>  修改用户     </li><li><code>/saveUser</code> 保存用户</li></ul></li><li><p>现在： <code>/user</code>   </p><ul><li>GET-获取用户    </li><li>DELETE-删除用户    </li><li>PUT-修改用户   </li><li>POST-保存用户</li></ul></li></ul></li><li><p>核心Filter：HiddenHttpMethodFilter</p></li></ul><p><strong>用法</strong></p><ul><li>1.开启页面表单的Rest功能</li></ul><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">spring</span><span class="token punctuation">:</span>  <span class="token key atrule">mvc</span><span class="token punctuation">:</span>    <span class="token key atrule">hiddenmethod</span><span class="token punctuation">:</span>      <span class="token key atrule">filter</span><span class="token punctuation">:</span>        <span class="token key atrule">enabled</span><span class="token punctuation">:</span> <span class="token boolean important">true   </span><span class="token comment" spellcheck="true">#开启页面表单的Rest功能</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>2.页面 form的属性method&#x3D;post，隐藏域 _method&#x3D;put、delete等（如果直接get或post，无需隐藏域）</li></ul><pre class="line-numbers language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>form</span> <span class="token attr-name">action</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>/user<span class="token punctuation">"</span></span> <span class="token attr-name">method</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>get<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span> <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>REST-GET提交<span class="token punctuation">"</span></span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>submit<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>form</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>form</span> <span class="token attr-name">action</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>/user<span class="token punctuation">"</span></span> <span class="token attr-name">method</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>post<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span> <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>REST-POST提交<span class="token punctuation">"</span></span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>submit<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>form</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>form</span> <span class="token attr-name">action</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>/user<span class="token punctuation">"</span></span> <span class="token attr-name">method</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>post<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>_method<span class="token punctuation">"</span></span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>hidden<span class="token punctuation">"</span></span> <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>DELETE<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span> <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>REST-DELETE 提交<span class="token punctuation">"</span></span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>submit<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>form</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>form</span> <span class="token attr-name">action</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>/user<span class="token punctuation">"</span></span> <span class="token attr-name">method</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>post<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>input</span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>_method<span class="token punctuation">"</span></span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>hidden<span class="token punctuation">"</span></span> <span class="token attr-name">value</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>PUT<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span>    &lt;input value="REST-PUT提交"type="submit" /><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>form</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>3.编写请求映射</li></ul><pre class="line-numbers language-java"><code class="language-java">    <span class="token annotation punctuation">@RequestMapping</span><span class="token punctuation">(</span>value <span class="token operator">=</span> <span class="token string">"/user"</span><span class="token punctuation">,</span>method <span class="token operator">=</span> RequestMethod<span class="token punctuation">.</span>GET<span class="token punctuation">)</span>    <span class="token keyword">public</span> String <span class="token function">getUser</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>        <span class="token keyword">return</span> <span class="token string">"GET-张三"</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@RequestMapping</span><span class="token punctuation">(</span>value <span class="token operator">=</span> <span class="token string">"/user"</span><span class="token punctuation">,</span>method <span class="token operator">=</span> RequestMethod<span class="token punctuation">.</span>POST<span class="token punctuation">)</span>    <span class="token keyword">public</span> String <span class="token function">saveUser</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>        <span class="token keyword">return</span> <span class="token string">"POST-张三"</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@RequestMapping</span><span class="token punctuation">(</span>value <span class="token operator">=</span> <span class="token string">"/user"</span><span class="token punctuation">,</span>method <span class="token operator">=</span> RequestMethod<span class="token punctuation">.</span>PUT<span class="token punctuation">)</span>    <span class="token keyword">public</span> String <span class="token function">putUser</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>        <span class="token keyword">return</span> <span class="token string">"PUT-张三"</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@RequestMapping</span><span class="token punctuation">(</span>value <span class="token operator">=</span> <span class="token string">"/user"</span><span class="token punctuation">,</span>method <span class="token operator">=</span> RequestMethod<span class="token punctuation">.</span>DELETE<span class="token punctuation">)</span>    <span class="token keyword">public</span> String <span class="token function">deleteUser</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>        <span class="token keyword">return</span> <span class="token string">"DELETE-张三"</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>Rest原理（表单提交要使用REST的时候）</p><ul><li><p>表单提交会带上 <code>_method=PUT</code></p></li><li><p>请求过来被 <code>HiddenHttpMethodFilter</code>拦截，检测请求是否正常，并且是POST</p><ul><li><p>获取到 <code>_method</code> 的值</p></li><li><p>兼容以下请求；<strong>PUT</strong>、<strong>DELETE</strong>、<strong>PATCH</strong></p></li><li><p>原生request（post），包装模式 requesWrapper 重写了getMethod方法，返回的是传入的值( <code>_method</code>)。</p></li><li><p>过滤器链放行的时候用 wrapper。以后的方法调用getMethod是调用 requesWrapper</p></li></ul></li></ul><h3 id="b-请求映射原理"><a href="#b-请求映射原理" class="headerlink" title="b.请求映射原理"></a>b.请求映射原理</h3><h1 id="Sponsor❤️"><a href="#Sponsor❤️" class="headerlink" title="Sponsor❤️"></a>Sponsor❤️</h1><p>您的支持是我不断前进的动力，如果您感觉本文对您有所帮助的话，可以考虑打赏一下本文，用以维持本博客的运营费用，拒绝白嫖，从你我做起！🥰🥰🥰</p><table>  <tbody>     <tr>         <td style="text-align:center;">支付宝</td>         <td style="text-align:center;">微信</td>     </tr>   <tr>    <td style="text-align:center;" ><img width="200" src="https://jwt1399.top/medias/reward/alipay.png"></td>          <td style="text-align:center;"><img width="200" src="https://jwt1399.top/medias/reward/sponor_wechat.png"></td>       </tr></tbody></table>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;原理篇包含自动配置工作流程、自定义starter开发、springboot程序启动流程、掌握SpringBoot内部工作流程、理解SpringBoot整合第三方技术的原理&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;小简从 0 开始学 Java</summary>
        
      
    
    
    
    <category term="Spring" scheme="https://jwt1399.top/categories/Spring/"/>
    
    
    <category term="SpringBoot" scheme="https://jwt1399.top/tags/SpringBoot/"/>
    
  </entry>
  
  <entry>
    <title>SpringBoot-实用篇</title>
    <link href="https://jwt1399.top/posts/37881.html"/>
    <id>https://jwt1399.top/posts/37881.html</id>
    <published>2022-09-09T16:20:41.000Z</published>
    <updated>2023-05-14T10:22:53.379Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>实用篇包含运维实用篇和开发实用篇，运维实用篇的定位是玩转配置；开发实用篇包含热部署、高级配置、测试进阶、内置数据层、监控等。</p></blockquote><p>小简从 0 开始学 Java 知识之 <a href="https://jwt1399.top/posts/29829.html">Java-学习路线</a> 中的《SpringBoot-实用篇》，不定期更新所学笔记，期待一年后的蜕变吧！&lt;有同样想法的小伙伴，可以联系我一起交流学习哦！&gt;</p><ul><li><p><input checked="" disabled="" type="checkbox"> 🚩时间安排：预计5天更新完</p></li><li><p><input checked="" disabled="" type="checkbox"> 🎯开始时间：09-11</p></li><li><p><input checked="" disabled="" type="checkbox"> 🎉结束时间：09-14</p></li><li><p><input checked="" disabled="" type="checkbox"> 🍀总结：</p></li><li><p>学习目标</p></li></ul><table><thead><tr><th>章节</th><th>学习目标</th></tr></thead><tbody><tr><td><font color="#cc0000"><b>应用篇</b></font></td><td>能够掌握SpringBoot程序多环境开发<br/>能够基于Linux系统发布SpringBoot工程<br/>能够解决线上灵活配置SpringBoot工程的需求</td></tr></tbody></table><h1 id="运维实用篇"><a href="#运维实用篇" class="headerlink" title="运维实用篇"></a>运维实用篇</h1><h2 id="1-打包与运行"><a href="#1-打包与运行" class="headerlink" title="1.打包与运行"></a>1.打包与运行</h2><h3 id="①程序打包"><a href="#①程序打包" class="headerlink" title="①程序打包"></a>①程序打包</h3><p>SpringBoot程序是基于Maven创建的，在Maven中提供有打包的指令，叫做package。</p><pre class="line-numbers language-java"><code class="language-java">mvn <span class="token keyword">package</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>本操作也可以在IDEA环境下执行。打包后会产生一个与工程名类似的jar文件，其名称是由<code>模块名+版本号+.jar</code>组成的。如果你写的测试类中有操作数据库的功能，打包时可以在IDEA-&gt;Maven中点击跳过测试按钮，再打包，来避免测试类影响数据库。</p><p><font color="#ff0000"><b>特别关注</b></font>：在使用向导创建SpringBoot工程时，pom.xml文件中会有如下配置，这一段配置千万不能删除，否则打包后无法正常执行程序。</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>build</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>plugins</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>plugin</span><span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework.boot<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-boot-maven-plugin<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>plugin</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>plugins</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>build</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="②程序运行"><a href="#②程序运行" class="headerlink" title="②程序运行"></a>②程序运行</h3><ul><li><p>1.安装JDK，且版本不低于打包时使用的JDK版本</p></li><li><p>2.程序包保存在&#x2F;usr&#x2F;local&#x2F;自定义目录中或$HOME下</p></li><li><p>3.执行运行指令就可以了</p></li></ul><p>前台运行</p><pre class="line-numbers language-bash"><code class="language-bash">java -jar 工程包名.jar<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>后台运行</p><pre class="line-numbers language-bash"><code class="language-bash">$ <span class="token function">nohup</span> java -jar 工程包名.jar <span class="token operator">></span> server.log 2<span class="token operator">></span><span class="token operator">&amp;</span>1 <span class="token operator">&amp;</span><span class="token comment" spellcheck="true">#命令解释</span><span class="token comment" spellcheck="true"># nohup 英文全称 no hang up（不挂起），用于在系统后台不挂断地运行命令，退出终端不会影响程序的运行</span><span class="token comment" spellcheck="true"># 最后一个&amp;表示把条命令放到后台执行</span><span class="token comment" spellcheck="true"># 2>&amp;1表示把标准错误输出和标准输出都定向到log中</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><table><thead><tr><th align="left">名称</th><th align="left">代码</th><th align="left">操作符</th><th align="left">Java中表示</th></tr></thead><tbody><tr><td align="left">标准输入(stdin)</td><td align="left">0</td><td align="left">&lt; 或 &lt;&lt;</td><td align="left">System.in</td></tr><tr><td align="left">标准输出(stdout)</td><td align="left">1</td><td align="left">&gt;, &gt;&gt;, 1&gt; 或 1&gt;&gt;</td><td align="left">System.out</td></tr><tr><td align="left">标准错误输出(stderr)</td><td align="left">2</td><td align="left">2&gt; 或 2&gt;&gt;</td><td align="left">System.err</td></tr></tbody></table><p>参考：<a href="https://www.jb51.net/article/169778.htm">深入理解Linux shell中2&gt;&amp;1的含义</a></p><p><strong>总结</strong></p><ol><li>SpringBoot工程可以基于java环境下独立运行jar文件启动服务</li><li>SpringBoot工程执行mvn命令package进行打包</li><li>执行jar命令：java –jar 工程名.jar</li></ol><h3 id="③jar运行机制"><a href="#③jar运行机制" class="headerlink" title="③jar运行机制"></a>③jar运行机制</h3><ul><li>jar包结构</li></ul><pre class="line-numbers language-powershell"><code class="language-powershell">$ tree <span class="token operator">-</span>L 2├── BOOT<span class="token operator">-</span>INF│   ├── classes│   ├── classpath<span class="token punctuation">.</span>idx│   ├── layers<span class="token punctuation">.</span>idx│   └── lib├── META<span class="token operator">-</span>INF│   ├── MANIFEST<span class="token punctuation">.</span>MF│   └── maven└── org    └── springframework<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>MANIFEST.MF 文件</li></ul><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">Manifest-Version</span><span class="token punctuation">:</span> <span class="token number">1.0</span><span class="token key atrule">Spring-Boot-Classpath-Index</span><span class="token punctuation">:</span> BOOT<span class="token punctuation">-</span>INF/classpath.idx<span class="token key atrule">Implementation-Title</span><span class="token punctuation">:</span> springboot_08_ssmp<span class="token key atrule">Implementation-Version</span><span class="token punctuation">:</span> 0.0.1<span class="token punctuation">-</span>SNAPSHOT<span class="token key atrule">Spring-Boot-Layers-Index</span><span class="token punctuation">:</span> BOOT<span class="token punctuation">-</span>INF/layers.idx<span class="token key atrule">Start-Class</span><span class="token punctuation">:</span> com.jianjian.SSMPApplication<span class="token key atrule">Spring-Boot-Classes</span><span class="token punctuation">:</span> BOOT<span class="token punctuation">-</span>INF/classes/<span class="token key atrule">Spring-Boot-Lib</span><span class="token punctuation">:</span> BOOT<span class="token punctuation">-</span>INF/lib/<span class="token key atrule">Build-Jdk-Spec</span><span class="token punctuation">:</span> <span class="token number">1.8</span><span class="token key atrule">Spring-Boot-Version</span><span class="token punctuation">:</span> 2.5.4<span class="token key atrule">Created-By</span><span class="token punctuation">:</span> Maven Jar Plugin 3.2.0<span class="token key atrule">Main-Class</span><span class="token punctuation">:</span> org.springframework.boot.loader.JarLauncher<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>运作机制</li></ul><ol><li><code>spring-boot-maven-plug</code>会打出一个特殊的包，包含Spring框架部分功能，原始工程内容，原始工程依赖的jar包</li><li>首先读取<code>MANIFEST.MF</code>文件中的<code>Main-Class</code>属性，用来标记执行java -jar命令后运行的类<code>JarLauncher</code></li><li>JarLauncher类执行时会找到<code>Start-Class</code>属性，也就是启动类类名</li><li>运行启动类时会运行当前工程的内容</li><li>运行当前工程时会使用依赖的jar包，从lib目录中查找</li></ol><h3 id="④异常排查"><a href="#④异常排查" class="headerlink" title="④异常排查"></a>④异常排查</h3><p>启动SpringBoot工程时，可能会遇到端口占用的问题。</p><ul><li>windows版</li></ul><pre class="line-numbers language-bash"><code class="language-bash"><span class="token comment" spellcheck="true"># 查询指定端口</span>$ <span class="token function">netstat</span> -ano <span class="token operator">|</span>findstr <span class="token string">"端口号"</span><span class="token comment" spellcheck="true"># 根据进程PID查询进程名称</span>$ tasklist <span class="token operator">|</span>findstr <span class="token string">"进程PID号"</span><span class="token comment" spellcheck="true"># 根据PID杀死任务</span>$ taskkill /F /PID <span class="token string">"进程PID号"</span><span class="token comment" spellcheck="true"># 根据进程名称杀死任务</span>$ taskkill -f -t -im <span class="token string">"进程名称"</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>Linux版</li></ul><pre class="line-numbers language-bash"><code class="language-bash"><span class="token comment" spellcheck="true"># 查询指定端口</span>$ <span class="token function">ps</span> -ef <span class="token operator">|</span> <span class="token function">grep</span> <span class="token string">"端口号"</span><span class="token comment" spellcheck="true"># 根据进程PID查询进程名称</span>$ <span class="token function">ps</span> -ef <span class="token operator">|</span> <span class="token function">grep</span> <span class="token string">"进程PID号"</span><span class="token comment" spellcheck="true"># 根据PID杀死任务</span>$ <span class="token function">kill</span> -9 进程PID号<span class="token comment" spellcheck="true"># 根据进程名称杀死任务</span>$ <span class="token function">kill</span> -9 进程名称<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="2-配置进阶"><a href="#2-配置进阶" class="headerlink" title="2.配置进阶"></a>2.配置进阶</h2><h3 id="①临时属性"><a href="#①临时属性" class="headerlink" title="①临时属性"></a>①临时属性</h3><blockquote><p>目前我们的程序包打好了，可以发布了。但是程序包打好以后，里面的配置都已经是固定的了，比如配置了服务器的端口是8080。如果我要启动项目，发现当前我的服务器上已经有应用启动起来并且占用了8080端口，这个时候就尴尬了。难道要重新把打包好的程序修改一下吗？比如我要把打包好的程序启动端口改成80。</p></blockquote><h4 id="a-服务器中使用临时属性"><a href="#a-服务器中使用临时属性" class="headerlink" title="a.服务器中使用临时属性"></a>a.服务器中使用临时属性</h4><p>SpringBoot提供了灵活的配置方式，如果你发现你的项目中有个别属性需要重新配置，可以使用临时属性的方式快速修改某些配置。方法也特别简单，在启动的时候添加上对应参数就可以了。</p><pre class="line-numbers language-bash"><code class="language-bash">$ java –jar springboot.jar –-server.port<span class="token operator">=</span>80<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>如果你发现要修改的属性不止一个，可以按照上述格式继续写，属性与属性之间使用空格分隔。</p><pre class="line-numbers language-bash"><code class="language-bash">$ java –jar springboot.jar –-server.port<span class="token operator">=</span>80 --logging.level.root<span class="token operator">=</span>debug<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><h4 id="b-开发环境中使用临时属性"><a href="#b-开发环境中使用临时属性" class="headerlink" title="b.开发环境中使用临时属性"></a>b.开发环境中使用临时属性</h4><p>打开SpringBoot引导类的运行界面，在里面找到配置项。其中Program arguments对应的位置就是添加临时属性的，可以加几个试试效果。</p><p><img src="https://img.jwt1399.top/img/202209131300448.png"></p><p>做到这里其实可以产生一个思考了，如果对java编程熟悉的小伙伴应该知道，我们运行main方法的时候，如果想使用main方法的参数，也就是下面的args参数，就是在上面这个位置添加的参数。</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>原来是这样，通过这个args就可以获取到参数。再来看我们的引导类是如何书写的</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>    SpringApplication<span class="token punctuation">.</span><span class="token function">run</span><span class="token punctuation">(</span>SSMPApplication<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">,</span>args<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>这个args参数居然传递给了run方法，看来在Idea中配置的临时参数就是通过这个位置传递到我们的程序中的。言外之意，这里如果不用这个args是不是就断开了外部传递临时属性的入口呢？是这样的，我们可以使用下面的调用方式，这样外部临时属性就无法进入到SpringBoot程序中了。</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>    SpringApplication<span class="token punctuation">.</span><span class="token function">run</span><span class="token punctuation">(</span>SSMPApplication<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>或者还可以使用如下格式来玩这个操作，就是将配置不写在配置文件中，直接写成一个字符串数组，传递给程序入口。当然，这种做法并没有什么实际开发意义。</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>    String<span class="token punctuation">[</span><span class="token punctuation">]</span> arg <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">String</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">;</span>    arg<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token string">"--server.port=8082"</span><span class="token punctuation">;</span>    SpringApplication<span class="token punctuation">.</span><span class="token function">run</span><span class="token punctuation">(</span>SSMPApplication<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">,</span> arg<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="②属性加载优先级"><a href="#②属性加载优先级" class="headerlink" title="②属性加载优先级"></a>②<strong>属性加载优先级</strong></h3><p>现在我们的程序配置受两个地方控制了，第一配置文件，第二临时属性。并且我们发现临时属性的加载优先级要高于配置文件的。那是否还有其他的配置方式呢？有的</p><p>打开官方文档中对应的内容，就可以查看配置读取的优先顺序。地址：<a href="https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.external-config">属性加载优先级 (spring.io)</a></p><p><img src="https://img.jwt1399.top/img/202209111913476.png"></p><p>有14种配置的位置，而我们现在使用的是这里面的2个。</p><ul><li><p>第3条Config data说的就是使用配置文件，</p></li><li><p>第11条Command line arguments说的就是使用命令行临时参数。</p></li></ul><p>这14种配置的顺序就是SpringBoot加载配置的顺序，这个列表<strong>上面的优先级低，下面的优先级高</strong>。</p><p><strong>总结</strong></p><ol><li>使用jar命令启动SpringBoot工程时可以使用临时属性替换配置文件中的属性</li><li>临时属性添加方式：java –jar 工程名.jar –-属性名&#x3D;值</li><li>多个临时属性之间使用空格分隔</li><li>临时属性必须是当前boot工程支持的属性，否则设置无效</li></ol><h3 id="③配置文件级别"><a href="#③配置文件级别" class="headerlink" title="③配置文件级别"></a>③配置文件级别</h3><p>SpringBoot提供4个级别配置文件。</p><ul><li><p>1.程序包所在目录中配置文件</p></li><li><p>2.程序包所在目录中config目录下配置文件</p></li><li><p>3.类路径下config目录下配置文件</p></li><li><p>4.类路径下配置文件（一直使用的是这个，也就是resources目录中的application.yml文件）</p></li></ul><p>其实就是提供给了4种配置文件书写的位置。上面4个文件的加载优先顺序为</p><ul><li><p>1级    file ：config&#x2F;application.yml <strong>【最高】</strong></p></li><li><p>2级    file ：application.yml</p></li><li><p>3级    classpath：config&#x2F;application.yml</p></li><li><p>4级    classpath：application.yml  <strong>【最低】</strong></p></li></ul><p>那为什么设计这种多种呢？说一个最典型的应用吧。</p><ul><li>场景A：你作为一个开发者，为了方便自己写代码，配置的数据库肯定是连接你自己本机的，咱们使用第4级别，也就是之前一直用的application.yml。</li><li>场景B：现在项目开发到了一个阶段，要联调测试了，连接的数据库是测试服务器的数据库，肯定要换一组配置吧。你可以选择把你之前的文件中的内容都改了，目前还不麻烦。</li><li>场景C：测试完了，一切OK。你继续写你的代码，你发现你原来写的配置文件被改成测试服务器的内容了，你要再改回来。现在明白了不？场景B中把你的内容都改掉了，你现在要重新改回来，以后呢？改来改去吗？</li></ul><p>解决方案很简单，用上面第3这个级别的配置文件就可以快速解决这个问题，再写一个配置就行了。两个配置文件共存，因为config目录中的配置加载优先级比你的高，所以配置项如果和级别4里面的内容相同就覆盖了，这样是不是很简单？</p><p>级别1和2什么时候使用呢？程序打包以后就要用这个级别了，管你程序里面配置写的是什么？我的级别高，可以轻松覆盖你，就不用考虑这些配置冲突的问题了。</p><p><strong>总结</strong></p><ol><li><p>配置文件分为4种</p><ul><li>项目类路径配置文件：开发人员本机开发与测试</li><li>项目类路径config目录中配置文件：项目经理整体调控</li><li>工程路径配置文件：运维人员配置涉密线上环境</li><li>工程路径config目录中配置文件：运维经理整体调控</li></ul></li><li><p>多层级配置文件间的属性采用叠加并覆盖的形式作用于程序</p></li></ol><h3 id="④自定义配置文件"><a href="#④自定义配置文件" class="headerlink" title="④自定义配置文件"></a>④自定义配置文件</h3><p>之前使用的配置文件都是application.yml，其实这个文件也是可以改名字的，这样方便维护。比如我2020年4月1日搞活动，走了一组配置，2020年5月1日活动取消，恢复原始配置，这个时候只需要重新更换一下配置文件就可以了。但是你总不能在原始配置文件上修改吧，不然搞完活动以后，活动的配置就留不下来了，不利于维护。</p><p>自定义配置文件方式有如下两种：</p><ul><li><strong>方式一：使用临时属性设置配置文件名，注意仅仅是名称，不要带扩展名</strong></li></ul><pre class="line-numbers language-bash"><code class="language-bash">--spring.config.name<span class="token operator">=</span>不带后缀的配置文件名字<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p><img src="https://img.jwt1399.top/img/202209111933227.png"></p><ul><li><strong>方式二：使用临时属性设置配置文件路径，这个是全路径名</strong></li></ul><pre class="line-numbers language-bash"><code class="language-bash">--spring.config.location<span class="token operator">=</span>classpath:/带后缀的配置文件名字<span class="token comment" spellcheck="true">#也可以设置加载多个配置文件</span>--spring.config.location<span class="token operator">=</span>classpath:/带后缀的配置文件名字,classpath:/带后缀的配置文件名字<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p><img src="https://img.jwt1399.top/img/202209111933941.png"></p><p><font color="#f0f"><b>温馨提示</b></font></p><p>​我们现在研究的都是SpringBoot单体项目，就是单服务器版本。其实企业开发现在更多的是使用基于SpringCloud技术的多服务器项目。这种配置方式上面的方式完全不一样，所有的服务器将不再设置自己的配置文件，而是通过配置中心获取配置，动态加载配置信息。为什么这样做？集中管理。</p><p><strong>总结</strong></p><ol><li>配置文件可以修改名称，通过启动参数设定</li><li>配置文件可以修改路径，通过启动参数设定</li><li>微服务开发中配置文件通过配置中心进行设置</li></ol><h2 id="3-多环境开发"><a href="#3-多环境开发" class="headerlink" title="3.多环境开发"></a>3.多环境开发</h2><p>程序最终要放到服务器上去运行。每个计算机环境不一样，这就是多环境。常见的多环境开发主要兼顾3种环境设置。</p><ul><li><p>开发环境——自己用的</p></li><li><p>测试环境——自己公司用的</p></li><li><p>生产环境——甲方爸爸用的</p></li></ul><p>因为这是绝对不同的三台电脑，所以环境肯定有所不同，比如连接的数据库不一样，设置的访问端口不一样等等。</p><img src="https://img.jwt1399.top/img/202209112043599.png"  style="zoom:67%;" /><h3 id="①yaml单一文件版"><a href="#①yaml单一文件版" class="headerlink" title="①yaml单一文件版"></a>①yaml单一文件版</h3><p>多环境开发就是针对不同的环境设置不同的配置属性即可。比如你自己开发时，配置你的端口如下：</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">server</span><span class="token punctuation">:</span>  <span class="token key atrule">port</span><span class="token punctuation">:</span> <span class="token number">80</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>如何设计两组环境呢？中间使用三个减号分隔开</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">server</span><span class="token punctuation">:</span>  <span class="token key atrule">port</span><span class="token punctuation">:</span> <span class="token number">80</span><span class="token punctuation">---</span><span class="token key atrule">server</span><span class="token punctuation">:</span>  <span class="token key atrule">port</span><span class="token punctuation">:</span> <span class="token number">81</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>如何区分两种环境呢？起名字呗</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">spring</span><span class="token punctuation">:</span>  <span class="token comment" spellcheck="true"># 环境1</span>    <span class="token key atrule">profiles</span><span class="token punctuation">:</span> pro   <span class="token key atrule">server</span><span class="token punctuation">:</span>    <span class="token key atrule">port</span><span class="token punctuation">:</span> <span class="token number">80</span><span class="token punctuation">---</span><span class="token key atrule">spring</span><span class="token punctuation">:</span>  <span class="token comment" spellcheck="true"># 环境2</span>    <span class="token key atrule">profiles</span><span class="token punctuation">:</span> dev  <span class="token key atrule">server</span><span class="token punctuation">:</span>    <span class="token key atrule">port</span><span class="token punctuation">:</span> <span class="token number">81</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>那用哪一个呢？设置默认启动哪个就可以了</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">spring</span><span class="token punctuation">:</span>    <span class="token key atrule">profiles</span><span class="token punctuation">:</span>        <span class="token key atrule">active</span><span class="token punctuation">:</span> pro<span class="token comment" spellcheck="true"># 启动pro</span><span class="token punctuation">---</span><span class="token key atrule">spring</span><span class="token punctuation">:</span>    <span class="token key atrule">profiles</span><span class="token punctuation">:</span> pro<span class="token key atrule">server</span><span class="token punctuation">:</span>    <span class="token key atrule">port</span><span class="token punctuation">:</span> <span class="token number">80</span><span class="token punctuation">---</span><span class="token key atrule">spring</span><span class="token punctuation">:</span>    <span class="token key atrule">profiles</span><span class="token punctuation">:</span> dev<span class="token key atrule">server</span><span class="token punctuation">:</span>    <span class="token key atrule">port</span><span class="token punctuation">:</span> <span class="token number">81</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>其中关于环境名称定义上述格式是过时格式，标准格式如下</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">spring</span><span class="token punctuation">:</span>    <span class="token key atrule">config</span><span class="token punctuation">:</span>        <span class="token key atrule">activate</span><span class="token punctuation">:</span>            <span class="token key atrule">on-profile</span><span class="token punctuation">:</span> pro<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p><strong>总结</strong></p><ol><li>多环境开发需要设置若干种常用环境，例如开发、生产、测试环境</li><li>yaml格式中设置多环境使用—区分环境设置边界</li><li>每种环境的区别在于加载的配置属性不同</li><li>启用某种环境时需要指定启动时使用该环境</li></ol><h3 id="②yaml多文件版"><a href="#②yaml多文件版" class="headerlink" title="②yaml多文件版"></a>②yaml多文件版</h3><p>将所有的配置都放在一个配置文件中，尤其是每一个配置应用场景都不一样，这显然不合理，于是就有了将一个配置文件拆分成多个配置文件的想法。拆分后，每个配置文件中写自己的配置，主配置文件中写清楚用哪一个配置文件就好了。</p><p><strong>主配置文件</strong></p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">spring</span><span class="token punctuation">:</span>    <span class="token key atrule">profiles</span><span class="token punctuation">:</span>        <span class="token key atrule">active</span><span class="token punctuation">:</span> pro<span class="token comment" spellcheck="true"># 启动pro</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p><strong>环境配置文件</strong></p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">server</span><span class="token punctuation">:</span>    <span class="token key atrule">port</span><span class="token punctuation">:</span> <span class="token number">80</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>环境配置文件因为每一个都是配置自己的项，所以连名字都不用写里面了。</p><p>那如何区分这是哪一组配置呢？使用文件名区分。文件的命名规则为：application-环境名.yml。</p><p><strong>application-pro.yaml</strong></p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">server</span><span class="token punctuation">:</span>    <span class="token key atrule">port</span><span class="token punctuation">:</span> <span class="token number">80</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p><strong>application-dev.yaml</strong></p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">server</span><span class="token punctuation">:</span>    <span class="token key atrule">port</span><span class="token punctuation">:</span> <span class="token number">81</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>在配置文件中，如果某些配置项所有环境都一样，可以将这些项写入到主配置中，只有哪些有区别的项才写入到环境配置文件中。</p><ul><li>主配置文件中设置公共配置（全局）</li><li>环境配置文件中设置冲突属性（局部）</li></ul><p><strong>总结</strong></p><ol><li><p>可以使用独立配置文件定义环境属性</p></li><li><p>独立配置文件便于线上系统维护更新并保障系统安全性</p></li></ol><h3 id="③properties多文件版"><a href="#③properties多文件版" class="headerlink" title="③properties多文件版"></a>③properties多文件版</h3><p>properties文件多环境配置仅支持多文件格式</p><p><strong>主配置文件</strong></p><pre class="line-numbers language-properties"><code class="language-properties"><span class="token attr-name">spring.profiles.active</span><span class="token punctuation">=</span><span class="token attr-value">pro</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p><strong>环境配置文件</strong></p><p><strong>application-pro.properties</strong></p><pre class="line-numbers language-properties"><code class="language-properties"><span class="token attr-name">server.port</span><span class="token punctuation">=</span><span class="token attr-value">80</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p><strong>application-dev.properties</strong></p><pre class="line-numbers language-properties"><code class="language-properties"><span class="token attr-name">server.port</span><span class="token punctuation">=</span><span class="token attr-value">81</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>文件的命名规则为：application-环境名.properties。</p><h3 id="④配置文件书写技巧"><a href="#④配置文件书写技巧" class="headerlink" title="④配置文件书写技巧"></a>④配置文件书写技巧</h3><p>作为程序员在搞配置的时候往往处于一种分久必合合久必分的局面。开始先写一起，后来为了方便维护就拆分。对于多环境开发也是如此，下面给大家说一下如何基于多环境开发做配置独立管理，务必掌握。</p><p><strong>a.准备工作</strong></p><p>将所有的配置根据功能对配置文件中的信息进行拆分，并制作成独立的配置文件，命名规则如下</p><ul><li>application-devDB.yml</li><li>application-devRedis.yml</li><li>application-devMVC.yml</li></ul><p><strong>b.使用</strong></p><p>使用include属性在激活指定环境的情况下，同时对多个环境进行加载使其生效，多个环境间使用逗号分隔</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">spring</span><span class="token punctuation">:</span>    <span class="token key atrule">profiles</span><span class="token punctuation">:</span>        <span class="token key atrule">active</span><span class="token punctuation">:</span> dev        <span class="token key atrule">include</span><span class="token punctuation">:</span> devDB<span class="token punctuation">,</span>devRedis<span class="token punctuation">,</span>devMVC<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>现在相当于加载dev配置时，再加载对应的3组配置，从结构上就很清晰，用了什么，对应的名称是什么</p><p><code>注意</code>：当主环境dev与其他环境有相同属性时，主环境属性生效；其他环境中有相同属性时，最后加载的环境属性生效</p><p><strong>c.改良</strong></p><p>但是上面的设置也有一个问题，比如我要切换dev环境为pro时，include也要修改。因为include属性只能使用一次，这就比较麻烦了。SpringBoot从2.4版开始使用group属性替代include属性，降低了配置书写量。简单说就是我先写好，你爱用哪个用哪个。</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">spring</span><span class="token punctuation">:</span>    <span class="token key atrule">profiles</span><span class="token punctuation">:</span>        <span class="token key atrule">active</span><span class="token punctuation">:</span> dev        <span class="token key atrule">group</span><span class="token punctuation">:</span>            <span class="token key atrule">"dev"</span><span class="token punctuation">:</span> devDB<span class="token punctuation">,</span>devRedis<span class="token punctuation">,</span>devMVC              <span class="token key atrule">"pro"</span><span class="token punctuation">:</span> proDB<span class="token punctuation">,</span>proRedis<span class="token punctuation">,</span>proMVC              <span class="token key atrule">"test"</span><span class="token punctuation">:</span> testDB<span class="token punctuation">,</span>testRedis<span class="token punctuation">,</span>testMVC<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="⑤多环境开发控制"><a href="#⑤多环境开发控制" class="headerlink" title="⑤多环境开发控制"></a>⑤多环境开发控制</h3><p>如果maven和SpringBoot同时设置了多环境的话怎么搞。要想处理这个冲突问题，你要先理清一个关系，究竟谁在多环境开发中其主导地位。maven是做什么的？项目构建管理的，最终生成代码包的，SpringBoot是干什么的？简化开发的。简化，又不是其主导作用。最终还是要靠maven来管理整个工程，所以SpringBoot应该听maven的。大体思想如下：</p><ul><li>先在maven环境中设置用什么具体的环境</li><li>在SpringBoot中读取maven设置的环境即可</li></ul><p><strong>a.maven中设置多环境（使用属性方式区分环境）</strong></p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>profiles</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>profile</span><span class="token punctuation">></span></span>     <span class="token comment" spellcheck="true">&lt;!--环境1--></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>id</span><span class="token punctuation">></span></span>env_dev<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>id</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>properties</span><span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>profile.active</span><span class="token punctuation">></span></span>dev<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>profile.active</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>properties</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>activation</span><span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>activeByDefault</span><span class="token punctuation">></span></span>true<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>activeByDefault</span><span class="token punctuation">></span></span><span class="token comment" spellcheck="true">&lt;!--默认启动环境--></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>activation</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>profile</span><span class="token punctuation">></span></span>      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>profile</span><span class="token punctuation">></span></span>     <span class="token comment" spellcheck="true">&lt;!--环境2--></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>id</span><span class="token punctuation">></span></span>env_pro<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>id</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>properties</span><span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>profile.active</span><span class="token punctuation">></span></span>pro<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>profile.active</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>properties</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>profile</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>profiles</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>b.SpringBoot中读取maven设置值</strong></p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">spring</span><span class="token punctuation">:</span>    <span class="token key atrule">profiles</span><span class="token punctuation">:</span>        <span class="token key atrule">active</span><span class="token punctuation">:</span> @profile.active@<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>上面的@属性名@就是读取maven中配置的属性值的语法格式。</p><p><strong>总结</strong></p><ol><li>当Maven与SpringBoot同时对多环境进行控制时，以Mavn为主，SpringBoot使用@..@占位符读取Maven对应的配置属性值</li><li>基于SpringBoot读取Maven配置属性的前提下，如果在IDEA下测试工程时pom.xml每次更新需要手动compile方可生效</li></ol><h2 id="4-日志"><a href="#4-日志" class="headerlink" title="4.日志"></a>4.日志</h2><p>日志就是记录程序日常运行的信息，主要作用如下：</p><ul><li>编程期调试代码</li><li>运营期记录信息<ul><li>记录日常运营重要信息（峰值流量、平均响应时长……）</li><li>记录应用报错信息（错误堆栈）</li><li>记录运维过程数据（扩容、宕机、报警……）</li></ul></li></ul><h3 id="①开启日志"><a href="#①开启日志" class="headerlink" title="①开启日志"></a>①开启日志</h3><p><strong>步骤①</strong>：添加日志记录操作</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@RestController</span><span class="token annotation punctuation">@RequestMapping</span><span class="token punctuation">(</span><span class="token string">"/books"</span><span class="token punctuation">)</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">BookController</span> <span class="token keyword">extends</span> <span class="token class-name">BaseClass</span><span class="token punctuation">{</span>    <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> Logger log <span class="token operator">=</span> LoggerFactory<span class="token punctuation">.</span><span class="token function">getLogger</span><span class="token punctuation">(</span>BookController<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token annotation punctuation">@GetMapping</span>    <span class="token keyword">public</span> String <span class="token function">getById</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>        log<span class="token punctuation">.</span><span class="token function">debug</span><span class="token punctuation">(</span><span class="token string">"debug..."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        log<span class="token punctuation">.</span><span class="token function">info</span><span class="token punctuation">(</span><span class="token string">"info..."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        log<span class="token punctuation">.</span><span class="token function">warn</span><span class="token punctuation">(</span><span class="token string">"warn..."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        log<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token string">"error..."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> <span class="token string">"springboot is running..."</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>上述代码中log对象就是用来记录日志的对象，下面的log.debug，log.info这些操作就是写日志的API了。</p><p><strong>步骤②</strong>：设置日志输出级别</p><p>日志设置好以后可以根据设置选择哪些参与记录。这里是根据日志的级别来设置的。日志的级别分为6种，分别是：</p><ul><li>TRACE：运行堆栈信息，使用率低</li><li><strong>DEBUG</strong>：程序员调试代码使用</li><li><strong>INFO</strong>：记录运维过程数据</li><li><strong>WARN</strong>：记录运维过程报警数据</li><li><strong>ERROR</strong>：记录错误堆栈信息</li><li>FATAL：灾难信息，合并计入ERROR</li></ul><p>一般情况下，开发时候使用DEBUG，上线后使用INFO，运维信息记录使用WARN即可。</p><p>下面就设置一下日志级别：</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token comment" spellcheck="true"># 开启debug模式，输出调试信息，常用于检查系统运行状况</span><span class="token key atrule">debug</span><span class="token punctuation">:</span> <span class="token boolean important">true</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>这么设置太简单粗暴了，日志系统通常都提供了细粒度的控制</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token comment" spellcheck="true"># 开启debug模式，输出调试信息，常用于检查系统运行状况</span><span class="token key atrule">debug</span><span class="token punctuation">:</span> <span class="token boolean important">true</span><span class="token comment" spellcheck="true"># 设置日志级别，root表示根节点，即整体应用日志级别</span><span class="token key atrule">logging</span><span class="token punctuation">:</span>    <span class="token key atrule">level</span><span class="token punctuation">:</span>        <span class="token key atrule">root</span><span class="token punctuation">:</span> debug<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>还可以再设置更细粒度的控制，可以直接控制指定包对应的日志输出级别</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">logging</span><span class="token punctuation">:</span>    <span class="token key atrule">level</span><span class="token punctuation">:</span>        <span class="token key atrule">root</span><span class="token punctuation">:</span> warn        <span class="token comment" spellcheck="true"># 设置某包包设置日志级别</span>      <span class="token key atrule">com.jianjian.config</span><span class="token punctuation">:</span> debug<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤③</strong>：设置日志组，控制指定包对应的日志输出级别</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">logging</span><span class="token punctuation">:</span>    <span class="token comment" spellcheck="true"># 设置日志组</span>    <span class="token key atrule">group</span><span class="token punctuation">:</span>        <span class="token comment" spellcheck="true"># 自定义组名，设置当前组中所包含的包</span>        <span class="token key atrule">book</span><span class="token punctuation">:</span> com.jianjian.controller        <span class="token key atrule">test</span><span class="token punctuation">:</span> com.jianjian.dao<span class="token punctuation">,</span>com.jianjian.service    <span class="token key atrule">level</span><span class="token punctuation">:</span>        <span class="token key atrule">root</span><span class="token punctuation">:</span> warn      <span class="token comment" spellcheck="true"># 为对应组设置日志级别</span>      <span class="token key atrule">book</span><span class="token punctuation">:</span> debug      <span class="token key atrule">test</span><span class="token punctuation">:</span> info<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>总结</strong></p><ol><li>日志用于记录开发调试与运维过程消息</li><li>日志的级别共6种，通常使用4种即可，分别是DEBUG，INFO，WARN，ERROR</li><li>可以通过日志组或代码包的形式进行日志显示级别的控制</li></ol><h3 id="②-Slf4j"><a href="#②-Slf4j" class="headerlink" title="②@Slf4j"></a>②@Slf4j</h3><p>每个类都要写创建日志记录对象，太繁琐，可以使用lombok给我们提供的工具类。</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@RestController</span><span class="token annotation punctuation">@RequestMapping</span><span class="token punctuation">(</span><span class="token string">"/books"</span><span class="token punctuation">)</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">BookController</span> <span class="token keyword">extends</span> <span class="token class-name">BaseClass</span><span class="token punctuation">{</span>    <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> Logger log <span class="token operator">=</span> LoggerFactory<span class="token punctuation">.</span><span class="token function">getLogger</span><span class="token punctuation">(</span>BookController<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//这一句可以不写了</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>导入lombok后使用@Slf4j注解搞定，日志对象名为log</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Slf4j</span><span class="token comment" spellcheck="true">//这个注解替代了上面那一行</span><span class="token annotation punctuation">@RestController</span><span class="token annotation punctuation">@RequestMapping</span><span class="token punctuation">(</span><span class="token string">"/books"</span><span class="token punctuation">)</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">BookController</span> <span class="token keyword">extends</span> <span class="token class-name">BaseClass</span><span class="token punctuation">{</span>    <span class="token annotation punctuation">@GetMapping</span>    <span class="token keyword">public</span> String <span class="token function">getById</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>        log<span class="token punctuation">.</span><span class="token function">debug</span><span class="token punctuation">(</span><span class="token string">"debug..."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        log<span class="token punctuation">.</span><span class="token function">info</span><span class="token punctuation">(</span><span class="token string">"info..."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        log<span class="token punctuation">.</span><span class="token function">warn</span><span class="token punctuation">(</span><span class="token string">"warn..."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        log<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token string">"error..."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> <span class="token string">"springboot is running..."</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="③日志输出格式"><a href="#③日志输出格式" class="headerlink" title="③日志输出格式"></a>③日志输出格式</h3><p>目前记录的格式是SpringBoot给我们提供的，如果想自定义控制就需要自己设置了。先分析一下当前日志的记录格式。</p><p><img src="https://img.jwt1399.top/img/202209121704365.png"></p><ul><li><p>级别用于做筛选过滤，PID与线程名用于做精准分析。</p></li><li><p>所属类&#x2F;接口名：当名称过长时，简化包名书写为首字母，甚至直接删除</p></li></ul><p>下面是模拟上方图片中日志格式的配置方式</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">logging</span><span class="token punctuation">:</span>    <span class="token key atrule">pattern</span><span class="token punctuation">:</span>        <span class="token key atrule">console</span><span class="token punctuation">:</span> <span class="token string">"%d %clr(%p) --- [%16t] %clr(%-40.40c){cyan} : %m %n"</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><ul><li>%d： 时间</li><li>%clr：带颜色显示</li><li>%p：   PID</li><li>%16t：%t代表线程 ，16代码显示长度为16  </li><li>%-40.40c：  %c代表所属类，-40代表左对齐显示长度40，.40长度超过40开始截断</li><li>{cyan}：自定义显示颜色</li><li>%m：消息</li><li>%n：换行</li></ul><p>详细可参考：<a href="https://blog.csdn.net/zhangyunfei1984/article/details/115419360">log4j 日志格式详解</a></p><h3 id="④日志文件"><a href="#④日志文件" class="headerlink" title="④日志文件"></a>④日志文件</h3><p>如何把日志记录到文件中，方便后期维护查阅。设置日志文件名即可。</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">logging</span><span class="token punctuation">:</span>    <span class="token key atrule">file</span><span class="token punctuation">:</span>        <span class="token key atrule">name</span><span class="token punctuation">:</span> server.log<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>为了便于维护，可以限制每个日志文件的大小，产生多个日志文件。</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">logging</span><span class="token punctuation">:</span>    <span class="token key atrule">logback</span><span class="token punctuation">:</span>        <span class="token key atrule">rollingpolicy</span><span class="token punctuation">:</span>            <span class="token key atrule">max-file-size</span><span class="token punctuation">:</span> 3KB  <span class="token comment" spellcheck="true"># 限制每个日志文件大小</span>            <span class="token key atrule">file-name-pattern</span><span class="token punctuation">:</span> server.%d<span class="token punctuation">{</span>yyyy<span class="token punctuation">-</span>MM<span class="token punctuation">-</span>dd<span class="token punctuation">}</span>.%i.log <span class="token comment" spellcheck="true">#日志文件命名规则</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>以上格式是基于logback日志技术设置每日日志文件的设置格式，要求容量到达3KB以后就转存信息到第二个文件中。文件命名规则中的%d标识日期，%i是一个递增变量，用于区分日志文件。</p><h1 id="开发实用篇"><a href="#开发实用篇" class="headerlink" title="开发实用篇"></a>开发实用篇</h1><h2 id="1-热部署"><a href="#1-热部署" class="headerlink" title="1.热部署"></a>1.热部署</h2><h3 id="①什么是热部署？"><a href="#①什么是热部署？" class="headerlink" title="①什么是热部署？"></a>①什么是热部署？</h3><p>简单说就是你程序改了，现在要重新启动服务器，嫌麻烦？不用重启，服务器会自己悄悄的把更新后的程序给重新加载一遍，这就是热部署。</p><p>热部署的功能是如何实现的呢？这就要分两种情况来说了，非springboot工程和springboot工程的热部署实现方式完全不一样。先说一下原始的非springboot项目是如何实现热部署的。</p><p><strong>非springboot项目热部署实现原理</strong></p><p>开发非springboot项目时，我们要制作一个web工程并通过tomcat启动，通常需要先安装tomcat服务器到磁盘中，开发的程序配置发布到安装的tomcat服务器上。如果想实现热部署的效果有两种做法，一种是在tomcat服务器的配置文件中进行配置。另一种做法是通过IDE工具进行配置。核心思想是一样的，就是使用服务器去监控其中加载的应用，发现产生了变化就重新加载一次。</p><p><strong>springboot项目热部署实现原理</strong></p><p>​基于springboot开发的web工程其实有一个显著的特征，就是tomcat服务器内置了。服务器是以一个对象的形式在spring容器中运行的。本来我们期望于tomcat服务器加载程序后由tomcat服务器盯着程序，你变化后我就重新启动重新加载，但是现在tomcat和我们的程序是平级的了，都是spring容器中的组件，这下就麻烦了，缺乏了一个直接的管理权，那该怎么做呢？简单，再搞一个程序X在spring容器中盯着你原始开发的程序A不就行了吗？确实，搞一个盯着程序A的程序X就行了，如果你自己开发的程序A变化了，那么程序X就命令tomcat容器重新加载程序A就OK了。并且这样做有一个好处，spring容器中东西不用全部重新加载一遍，只需要重新加载你开发的程序那一部分就可以了，这下效率又高了，挺好。</p><p>​下面就说说，怎么搞出来这么一个程序X，肯定不是我们自己手写了，springboot早就做好了，搞一个坐标导入进去就行了。</p><h3 id="②手动启动热部署"><a href="#②手动启动热部署" class="headerlink" title="②手动启动热部署"></a>②手动启动热部署</h3><p><strong>步骤①</strong>：导入开发者工具对应的坐标</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework.boot<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-boot-devtools<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>optional</span><span class="token punctuation">></span></span>true<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>optional</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤②</strong>：构建项目，可以使用快捷键（ctrl+F9或者command+F9）激活此功能</p><p><img src="https://img.jwt1399.top/img/202209122115363.png"></p><p><strong>底层的工作工程原理：重启与重载</strong></p><p>​一个springboot项目在运行时实际上是分两个过程进行的，根据加载的东西不同，划分成base类加载器与restart类加载器。</p><ul><li>base类加载器：用来加载jar包中的类，jar包中的类和配置文件由于不会发生变化，因此不管加载多少次，加载的内容不会发生变化</li><li>restart类加载器：用来加载开发者自己开发的类、配置文件、页面等信息，这一类文件受开发者影响</li></ul><p>​当springboot项目启动时，base类加载器执行，加载jar包中的信息后，restart类加载器执行，加载开发者制作的内容。当执行构建项目后，由于jar中的信息不会变化，因此base类加载器无需再次执行，所以仅仅运行restart类加载即可，也就是将开发者自己制作的内容重新加载就行了，这就完成了一次热部署的过程，也可以说热部署的过程实际上是重新加载restart类加载器中的信息。</p><h3 id="③自动启动热部署"><a href="#③自动启动热部署" class="headerlink" title="③自动启动热部署"></a>③自动启动热部署</h3><p>上述过程每次进行热部署都需要开发者手工操作，不管是点击按钮还是快捷键都需要开发者手工执行。</p><p>自动热部署其实就是设计一个开关，打开这个开关后，IDE工具就可以自动热部署。因此这个操作和IDE工具有关，以下以idea为例设置idea中启动热部署</p><p><strong>步骤①</strong>：设置自动构建项目</p><p>打开【File】，选择【settings…】,在面板左侧的菜单中找到【Compile】选项，然后勾选【Build project automatically】，意思是自动构建项目</p><p><img src="https://img.jwt1399.top/img/202209122128893.png"></p><p><strong>步骤②</strong>：允许在程序运行时进行自动构建</p><ul><li>老版IDEA操作方法：</li></ul><p>使用快捷键【Ctrl+Alt+Shift+&#x2F; 或者 command+option+shift+&#x2F;】打开维护面板，选择第1项【Registry…】</p><p><img src="https://img.jwt1399.top/img/202209122136780.png"></p><p>在选项中搜索comple，然后勾选对应项即可</p><img src="https://img.jwt1399.top/img/202209122136899.png"  style="zoom:80%;" /><ul><li>新版本IDEA操作方法</li></ul><p><img src="https://img.jwt1399.top/img/202209122136714.png"></p><p><font color="#ff0000"><b>关注</b></font>：如果你每敲一个字母，服务器就重新构建一次，这未免有点太频繁了，所以idea设置当idea工具失去焦点5秒后进行热部署。其实就是你从idea工具中切换到其他工具时进行热部署，比如改完程序需要到浏览器上去调试，这个时候idea就自动进行热部署操作。</p><h3 id="④热部署范围配置"><a href="#④热部署范围配置" class="headerlink" title="④热部署范围配置"></a>④热部署范围配置</h3><p>其实并不是所有的文件修改都会激活热部署的，原因在于在开发者工具中有一组配置，当满足了配置中的条件后，才会启动热部署，配置中默认不参与热部署的目录信息如下</p><ul><li>&#x2F;META-INF&#x2F;maven</li><li>&#x2F;META-INF&#x2F;resources</li><li>&#x2F;resources</li><li>&#x2F;static</li><li>&#x2F;public</li><li>&#x2F;templates</li></ul><p>以上目录中的文件如果发生变化，是不参与热部署的。如果想修改配置，可以通过application.yml文件进行设定哪些文件不参与热部署操作</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">spring</span><span class="token punctuation">:</span>  <span class="token key atrule">devtools</span><span class="token punctuation">:</span>    <span class="token key atrule">restart</span><span class="token punctuation">:</span>      <span class="token comment" spellcheck="true"># 设置不参与热部署的文件或文件夹</span>      <span class="token key atrule">exclude</span><span class="token punctuation">:</span> static/**<span class="token punctuation">,</span>public/**<span class="token punctuation">,</span>config/application.yml<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="⑤关闭热部署"><a href="#⑤关闭热部署" class="headerlink" title="⑤关闭热部署"></a>⑤关闭热部署</h3><p>线上环境运行时是不可能使用热部署功能的，所以需要强制关闭此功能，通过配置可以关闭此功能。</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">spring</span><span class="token punctuation">:</span>  <span class="token key atrule">devtools</span><span class="token punctuation">:</span>    <span class="token key atrule">restart</span><span class="token punctuation">:</span>      <span class="token key atrule">enabled</span><span class="token punctuation">:</span> <span class="token boolean important">false</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>如果当心配置文件层级过多导致相符覆盖最终引起配置失效，可以提高配置的层级，在更高层级中配置关闭热部署。例如在启动容器前通过系统属性设置关闭热部署功能。</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@SpringBootApplication</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">SSMPApplication</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>        System<span class="token punctuation">.</span><span class="token function">setProperty</span><span class="token punctuation">(</span><span class="token string">"spring.devtools.restart.enabled"</span><span class="token punctuation">,</span><span class="token string">"false"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        SpringApplication<span class="token punctuation">.</span><span class="token function">run</span><span class="token punctuation">(</span>SSMPApplication<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="2-配置进阶-1"><a href="#2-配置进阶-1" class="headerlink" title="2.配置进阶"></a>2.配置进阶</h2><h3 id="①-ConfigurationProperties"><a href="#①-ConfigurationProperties" class="headerlink" title="①@ConfigurationProperties"></a>①@ConfigurationProperties</h3><p>在基础篇学习了@ConfigurationProperties注解，此注解的作用是用来为bean绑定属性的。</p><p>**a.**开发者可以在yml配置文件中以对象的格式添加若干属性</p><pre class="line-numbers language-YML"><code class="language-YML">servers:  ip-address: 192.168.0.1   port: 2345  timeout: -1<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>**b.**然后再开发一个用来封装数据的实体类，注意要提供属性对应的setter方法</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Component</span><span class="token annotation punctuation">@Data</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ServerConfig</span> <span class="token punctuation">{</span>    <span class="token keyword">private</span> String ipAddress<span class="token punctuation">;</span>    <span class="token keyword">private</span> <span class="token keyword">int</span> port<span class="token punctuation">;</span>    <span class="token keyword">private</span> <span class="token keyword">long</span> timeout<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>**c.**使用<code>@ConfigurationProperties</code>注解就可以将配置中的属性值关联到开发的模型类上</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Component</span><span class="token annotation punctuation">@Data</span><span class="token annotation punctuation">@ConfigurationProperties</span><span class="token punctuation">(</span>prefix <span class="token operator">=</span> <span class="token string">"servers"</span><span class="token punctuation">)</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ServerConfig</span> <span class="token punctuation">{</span>    <span class="token keyword">private</span> String ipAddress<span class="token punctuation">;</span>    <span class="token keyword">private</span> <span class="token keyword">int</span> port<span class="token punctuation">;</span>    <span class="token keyword">private</span> <span class="token keyword">long</span> timeout<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>这样加载对应bean的时候就可以直接加载配置属性值了。</p><p>**d.**使用@ConfigurationProperties注解时，会出现一个提示信息</p><p><img src="https://img.jwt1399.top/img/202209171203135.png"></p><p>出现这个提示后只需要添加一个坐标此提醒就消失了</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework.boot<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-boot-configuration-processor<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><blockquote><p>但是目前我们学的都是给自定义的bean使用这种形式加载属性值，如果是第三方的bean呢？能不能用这种形式加载属性值呢？为什么会提出这个疑问？原因就在于当前@ConfigurationProperties注解是写在类定义的上方，而第三方开发的bean源代码不是你自己书写的，你也不可能到源代码中去添加@ConfigurationProperties注解，这种问题该怎么解决呢？下面就来说说这个问题。</p></blockquote><p>使用@ConfigurationProperties注解其实也可以为第三方bean加载属性，格式特殊一点而已。</p><p><strong>步骤①</strong>：使用@Bean注解定义第三方bean</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Bean</span><span class="token keyword">public</span> DruidDataSource <span class="token function">datasource</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>    DruidDataSource ds <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">DruidDataSource</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> ds<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤②</strong>：在yml中定义要绑定的属性，注意datasource此时全小写</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">datasource</span><span class="token punctuation">:</span>  <span class="token key atrule">driverClassName</span><span class="token punctuation">:</span> com.mysql.jdbc.Driver<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p><strong>步骤③</strong>：使用@ConfigurationProperties注解为第三方bean进行属性绑定，注意前缀是全小写的datasource</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Bean</span><span class="token annotation punctuation">@ConfigurationProperties</span><span class="token punctuation">(</span>prefix <span class="token operator">=</span> <span class="token string">"datasource"</span><span class="token punctuation">)</span><span class="token keyword">public</span> DruidDataSource <span class="token function">datasource</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>    DruidDataSource ds <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">DruidDataSource</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> ds<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>操作方式完全一样，只不过@ConfigurationProperties注解不仅能添加到类上，还可以添加到方法上，添加到类上是为spring容器管理的当前类的对象绑定属性，添加到方法上是为spring容器管理的当前方法的返回值对象绑定属性，其实本质上都一样。</p><h3 id="②-EnableConfigurationProperties"><a href="#②-EnableConfigurationProperties" class="headerlink" title="②@EnableConfigurationProperties"></a>②@EnableConfigurationProperties</h3><blockquote><p>目前我们定义bean不是通过<code>@Component</code>定义就是通过<code>@Bean</code>定义，使用<code>@ConfigurationProperties</code>注解可以为bean进行属性绑定，那在一个业务系统中，哪些bean通过注解@ConfigurationProperties去绑定属性了呢？因为这个注解不仅可以写在类上，还可以写在方法上，所以找起来就比较麻烦了。</p><p>为了解决这个问题，spring给我们提供了一个全新的注解，专门标注使用<code>@ConfigurationProperties</code>注解绑定属性的bean是哪些。这个注解叫做<code>@EnableConfigurationProperties</code>。具体如何使用呢？</p></blockquote><p><strong>步骤①</strong>：在配置类上开启<code>@EnableConfigurationProperties</code>注解，并标注要使用@ConfigurationProperties注解绑定属性的类</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@SpringBootApplication</span><span class="token annotation punctuation">@EnableConfigurationProperties</span><span class="token punctuation">(</span>ServerConfig<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">SpringbootConfigurationApplication</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤②</strong>：在对应的类上直接使用@ConfigurationProperties进行属性绑定</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Data</span><span class="token annotation punctuation">@ConfigurationProperties</span><span class="token punctuation">(</span>prefix <span class="token operator">=</span> <span class="token string">"servers"</span><span class="token punctuation">)</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ServerConfig</span> <span class="token punctuation">{</span>    <span class="token keyword">private</span> String ipAddress<span class="token punctuation">;</span>    <span class="token keyword">private</span> <span class="token keyword">int</span> port<span class="token punctuation">;</span>    <span class="token keyword">private</span> <span class="token keyword">long</span> timeout<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>有人感觉这没区别啊？注意观察，现在绑定属性的ServerConfig类并没有声明<code>@Component</code>注解。当使用<code>@EnableConfigurationProperties</code>注解时，spring会默认将其标注的类定义为bean，因此无需再次声明<code>@Component</code>注解了。</p><p><strong>总结</strong></p><ol><li>使用@ConfigurationProperties可以为使用@Bean声明的第三方bean绑定属性</li><li>当使用@EnableConfigurationProperties声明进行属性绑定的bean后，无需使用@Component注解再次进行bean声明</li></ol><h3 id="③松散绑定"><a href="#③松散绑定" class="headerlink" title="③松散绑定"></a>③松散绑定</h3><p>在进行属性绑定时，可能会遇到如下情况，为了进行标准命名，开发者会将属性名严格按照驼峰命名法书写，在yml配置文件中将datasource修改为dataSource，此时程序可以正常运行</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">dataSource</span><span class="token punctuation">:</span>  <span class="token key atrule">driverClassName</span><span class="token punctuation">:</span> com.mysql.jdbc.Driver<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>然后又将代码中的前缀datasource修改为dataSource</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Bean</span><span class="token annotation punctuation">@ConfigurationProperties</span><span class="token punctuation">(</span>prefix <span class="token operator">=</span> <span class="token string">"dataSource"</span><span class="token punctuation">)</span><span class="token keyword">public</span> DruidDataSource <span class="token function">datasource</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>    DruidDataSource ds <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">DruidDataSource</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> ds<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>此时就发生了编译错误，而且并不是idea工具导致的，运行后依然会出现问题，配置属性名dataSource是无效的</p><pre class="line-numbers language-bash"><code class="language-bash">Configuration property name <span class="token string">'dataSource'</span> is not valid:    Invalid characters: <span class="token string">'S'</span>    Bean: datasource    Reason: Canonical names should be kebab-case <span class="token punctuation">(</span><span class="token string">'-'</span> separated<span class="token punctuation">)</span>, lowercase alpha-numeric characters and must start with a letterAction:Modify <span class="token string">'dataSource'</span> so that it conforms to the canonical names requirements.<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>为什么会出现这种问题，这就要来说一说springboot进行属性绑定时的一个重要知识点了，有关属性名称的宽松绑定，也可以称为宽松绑定。</p><blockquote><p>什么是宽松绑定？实际上是springboot进行编程时人性化设计的一种体现，即配置文件中的命名格式与变量名的命名格式可以进行格式上的最大化兼容。兼容到什么程度呢？几乎主流的命名格式都支持，例如：</p></blockquote><p>在ServerConfig中的ipAddress属性名</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Component</span><span class="token annotation punctuation">@Data</span><span class="token annotation punctuation">@ConfigurationProperties</span><span class="token punctuation">(</span>prefix <span class="token operator">=</span> <span class="token string">"servers"</span><span class="token punctuation">)</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ServerConfig</span> <span class="token punctuation">{</span>    <span class="token keyword">private</span> String ipAddress<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>可以与下面的配置属性名规则全兼容</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">servers</span><span class="token punctuation">:</span>  <span class="token key atrule">ipAddress</span><span class="token punctuation">:</span> 192.168.0.2       <span class="token comment" spellcheck="true"># 驼峰模式</span>  <span class="token key atrule">ip_address</span><span class="token punctuation">:</span> 192.168.0.2      <span class="token comment" spellcheck="true"># 下划线模式</span>  <span class="token key atrule">ip-address</span><span class="token punctuation">:</span> 192.168.0.2      <span class="token comment" spellcheck="true"># 烤肉串模式</span>  <span class="token key atrule">IP_ADDRESS</span><span class="token punctuation">:</span> 192.168.0.2      <span class="token comment" spellcheck="true"># 常量模式</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>也可以说，以上4种模式最终都可以匹配到ipAddress这个属性名。为什么这样呢？原因就是在进行匹配时，配置中的名称要去掉中划线和下划线后，忽略大小写的情况下去与java代码中的属性名进行忽略大小写的等值匹配，以上4种命名去掉下划线中划线忽略大小写后都是一个词ipaddress，java代码中的属性名忽略大小写后也是ipaddress，这样就可以进行等值匹配了，这就是为什么这4种格式都能匹配成功的原因。不过springboot官方推荐使用烤肉串模式，也就是中划线模式。</p><p>到这里我们掌握了一个知识点，就是命名的规范问题。再来看开始出现的编程错误信息</p><pre class="line-numbers language-bash"><code class="language-bash">Configuration property name <span class="token string">'dataSource'</span> is not valid:    Invalid characters: <span class="token string">'S'</span>    Bean: datasource    Reason: Canonical names should be kebab-case <span class="token punctuation">(</span><span class="token string">'-'</span> separated<span class="token punctuation">)</span>, lowercase alpha-numeric characters and must start with a letterAction:Modify <span class="token string">'dataSource'</span> so that it conforms to the canonical names requirements.<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>其中Reason描述了报错的原因，规范的名称应该是烤肉串(kebab)模式(case)，即使用-分隔，使用小写字母数字作为标准字符，且必须以字母开头。然后再看我们写的名称dataSource，就不满足上述要求。闹了半天，在书写前缀时，这个词不是随意支持的，必须使用上述标准。</p><p>最后说一句，以上规则仅针对springboot中@ConfigurationProperties注解进行属性绑定时有效，对@Value注解进行属性映射无效。</p><p><strong>总结</strong></p><ol><li>@ConfigurationProperties绑定属性时支持属性名宽松绑定，这个宽松体现在属性名的命名规则上</li><li>@Value注解不支持松散绑定规则</li><li>绑定前缀名推荐采用烤肉串命名规则，即使用中划线做分隔符</li></ol><h3 id="④计量单位绑定"><a href="#④计量单位绑定" class="headerlink" title="④计量单位绑定"></a>④计量单位绑定</h3><p>在配置中，我们书写如下配置值，其中第三项超时时间timeout描述了服务器操作超时时间，当前值是-1表示永不超时。</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">servers</span><span class="token punctuation">:</span>  <span class="token key atrule">ip-address</span><span class="token punctuation">:</span> 192.168.0.1   <span class="token key atrule">port</span><span class="token punctuation">:</span> <span class="token number">2345</span>  <span class="token key atrule">timeout</span><span class="token punctuation">:</span> <span class="token number">-1</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><blockquote><p>但是每个人对这个值的理解会产生不同，比如线上服务器完成一次主从备份，配置超时时间240，这个240如果单位是秒就是超时时间4分钟，如果单位是分钟就是超时时间4小时。这个时候问题就来了，怎么解决这个误会？</p></blockquote><p>除了加强约定之外，springboot充分利用了JDK8中提供的全新的计量单位的新数据类型，从根本上解决这个问题。以下模型类中添加了两个JDK8中新增的类，分别是Duration和DataSize</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Component</span><span class="token annotation punctuation">@Data</span><span class="token annotation punctuation">@ConfigurationProperties</span><span class="token punctuation">(</span>prefix <span class="token operator">=</span> <span class="token string">"servers"</span><span class="token punctuation">)</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ServerConfig</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@DurationUnit</span><span class="token punctuation">(</span>ChronoUnit<span class="token punctuation">.</span>HOURS<span class="token punctuation">)</span>    <span class="token keyword">private</span> Duration serverTimeOut<span class="token punctuation">;</span>    <span class="token annotation punctuation">@DataSizeUnit</span><span class="token punctuation">(</span>DataUnit<span class="token punctuation">.</span>MEGABYTES<span class="token punctuation">)</span>    <span class="token keyword">private</span> DataSize dataSize<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li><p><strong>Duration</strong>：表示时间间隔，可以通过@DurationUnit注解描述时间单位，例如上例中描述的单位为小时（ChronoUnit.HOURS）</p></li><li><p><strong>DataSize</strong>：表示存储空间，可以通过@DataSizeUnit注解描述存储空间单位，例如上例中描述的单位为MB（DataUnit.MEGABYTES）</p></li></ul><p>​使用上述两个单位就可以有效避免因沟通不同步或文档不健全导致的信息不对称问题，从根本上解决了问题，避免产生误读。</p><h3 id="⑤属性校验"><a href="#⑤属性校验" class="headerlink" title="⑤属性校验"></a>⑤属性校验</h3><blockquote><p>在yml文件中书写配置时由于无法感知模型类中的数据类型，就会出现类型不匹配的问题，比如代码中需要int类型，配置中给了非法的数值，例如写一个“a”，这种数据肯定无法有效的绑定，还会引发错误。</p><p>SpringBoot给出了强大的数据校验功能，可以有效的避免此类问题的发生。在javaEE的JSR303规范中给出了具体的数据校验标准，开发者可以根据自己的需要选择对应的校验框架，此处使用Hibernate提供的校验框架来作为实现进行数据校验。</p></blockquote><p><strong>步骤①</strong>：开启校验框架</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token comment" spellcheck="true">&lt;!--1.导入JSR303规范接口--></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>javax.validation<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>validation-api<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span class="token comment" spellcheck="true">&lt;!--使用hibernate框架提供的校验器做实现--></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.hibernate.validator<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>hibernate-validator<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤②</strong>：在需要开启校验功能的类上使用注解@Validated开启校验功能</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Component</span><span class="token annotation punctuation">@Data</span><span class="token annotation punctuation">@ConfigurationProperties</span><span class="token punctuation">(</span>prefix <span class="token operator">=</span> <span class="token string">"servers"</span><span class="token punctuation">)</span><span class="token annotation punctuation">@Validated</span>  <span class="token comment" spellcheck="true">//开启对当前bean的属性注入校验</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ServerConfig</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤③</strong>：对具体的字段设置校验规则</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Component</span><span class="token annotation punctuation">@Data</span><span class="token annotation punctuation">@ConfigurationProperties</span><span class="token punctuation">(</span>prefix <span class="token operator">=</span> <span class="token string">"servers"</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true">//开启对当前bean的属性注入校验</span><span class="token annotation punctuation">@Validated</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ServerConfig</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">//设置具体的规则</span>    <span class="token annotation punctuation">@Max</span><span class="token punctuation">(</span>value <span class="token operator">=</span> <span class="token number">8888</span><span class="token punctuation">,</span>message <span class="token operator">=</span> <span class="token string">"最大值不能超过8888"</span><span class="token punctuation">)</span>    <span class="token annotation punctuation">@Min</span><span class="token punctuation">(</span>value <span class="token operator">=</span> <span class="token number">202</span><span class="token punctuation">,</span>message <span class="token operator">=</span> <span class="token string">"最小值不能低于202"</span><span class="token punctuation">)</span>    <span class="token keyword">private</span> <span class="token keyword">int</span> port<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>通过设置数据格式校验，就可以有效避免非法数据加载。</p><h3 id="⑥属性注入问题"><a href="#⑥属性注入问题" class="headerlink" title="⑥属性注入问题"></a>⑥属性注入问题</h3><p>先把问题描述一下，开发者连接数据库操作，但是运行程序后显示的信息是密码错误。</p><pre class="line-numbers language-bash"><code class="language-bash">java.sql.SQLException: Access denied <span class="token keyword">for</span> user <span class="token string">'root'</span>@<span class="token string">'localhost'</span> <span class="token punctuation">(</span>using password: YES<span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>这是用户名和密码不匹配，就是密码输入错了，但是问题就在于密码并没有输入错误。如果是初学者，估计这会心态就崩了，我密码没错啊，你怎么能说我有错误呢？来看看用户名密码的配置是如何写的：</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">spring</span><span class="token punctuation">:</span>  <span class="token key atrule">datasource</span><span class="token punctuation">:</span>    <span class="token key atrule">driver-class-name</span><span class="token punctuation">:</span> com.mysql.cj.jdbc.Driver    <span class="token key atrule">url</span><span class="token punctuation">:</span> jdbc<span class="token punctuation">:</span>mysql<span class="token punctuation">:</span>//localhost<span class="token punctuation">:</span>3306/ssm_db<span class="token punctuation">?</span>serverTimezone=UTC    <span class="token key atrule">username</span><span class="token punctuation">:</span> root    <span class="token key atrule">password</span><span class="token punctuation">:</span> <span class="token number">0127</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>这名开发者的生日是1月27日，所以密码就使用了0127，其实问题就出在这里了。</p><p>在整数相关知识中有这么一句话，<strong>支持二进制，八进制，十六进制</strong></p><img src="https://img.jwt1399.top/img/202209131842566.png" style="zoom:80%;" /><p>这个问题就出在这里了，因为0127在开发者眼中是一个字符串“0127”，但是在springboot看来，这就是一个数字，而且是一个八进制的数字。当后台使用String类型接收数据时，如果配置文件中配置了一个整数值，他是先按照整数进行处理，读取后再转换成字符串。巧了，0127撞上了八进制的格式，所以后台先转换为十进制数字87再读取为“87”，这就导致密码错误的报错。解决方法为加上引号</p><p>这里提两个注意点，第一，字符串标准书写加上引号包裹，养成习惯，第二，遇到0开头的数据多注意吧。</p><h2 id="3-测试进阶"><a href="#3-测试进阶" class="headerlink" title="3.测试进阶"></a>3.测试进阶</h2><h3 id="①加载测试专用属性"><a href="#①加载测试专用属性" class="headerlink" title="①加载测试专用属性"></a>①加载测试专用属性</h3><p>测试过程本身并不是一个复杂的过程，但是很多情况下测试时需要模拟一些线上情况，或者模拟一些特殊情况。如果当前环境按照线上环境已经设定好了，这个时候我们能不能每次测试的时候都去修改源码 application.yml 中的配置进行测试呢？显然是不行的。每次测试前改过来，每次测试后改回去，这太麻烦了。于是我们就想，需要在测试环境中创建一组临时属性，去覆盖我们源码中设定的属性，这样测试用例就相当于是一个独立的环境，能够独立测试，这样就方便多了。</p><p><strong>临时属性</strong></p><p>SpringBoot已经为我们开发者早就想好了这种问题该如何解决，并且提供了对应的功能入口。在测试用例程序中，可以通过对注解@SpringBootTest添加属性来模拟临时属性，具体如下：</p><pre class="line-numbers language-JAVA"><code class="language-JAVA">//properties属性可以为当前测试用例添加临时的属性配置@SpringBootTest(properties = {"test.prop=testValue1"})public class PropertiesAndArgsTest {    @Value("${test.prop}")    private String msg;        @Test    void testProperties(){        System.out.println(msg);    }}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>使用注解@SpringBootTest的properties属性就可以为当前测试用例添加临时的属性，覆盖源码配置文件中对应的属性值进行测试。</p><p><strong>临时参数</strong></p><p>通过命令行参数也可以设置属性值。而且线上启动程序时，通常都会添加一些专用的配置信息。</p><pre class="line-numbers language-JAVA"><code class="language-JAVA">//args属性可以为当前测试用例添加临时的命令行参数@SpringBootTest(args={"--test.prop=testValue2"})public class PropertiesAndArgsTest {        @Value("${test.prop}")    private String msg;        @Test    void testProperties(){        System.out.println(msg);    }}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>使用注解@SpringBootTest的args属性就可以为当前测试用例模拟命令行参数并进行测试。</p><p><strong>总结</strong></p><p>加载测试临时属性可以通过注解@SpringBootTest的properties和args属性进行设定，此设定应用范围仅适用于当前测试用例</p><h3 id="②加载测试专用配置"><a href="#②加载测试专用配置" class="headerlink" title="②加载测试专用配置"></a>②加载测试专用配置</h3><p>临时配置一些专用于测试环境的bean的需求，现在我们的需求其实就是在测试环境中再添加一个配置类，然后启动测试环境时，生效此配置就行了。其实做法和spring环境中加载多个配置信息的方式完全一样。具体操作步骤如下：</p><p><strong>步骤①</strong>：在测试包test中创建专用的测试环境配置类</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Configuration</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">MsgConfig</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Bean</span>    <span class="token keyword">public</span> String <span class="token function">msg</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>        <span class="token keyword">return</span> <span class="token string">"bean msg"</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>上述配置仅用于演示当前实验效果，实际开发可不能这么注入String类型的数据</p><p><strong>步骤②</strong>：在启动测试环境时，导入测试环境专用的配置类，使用@Import注解即可实现</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@SpringBootTest</span><span class="token annotation punctuation">@Import</span><span class="token punctuation">(</span><span class="token punctuation">{</span>MsgConfig<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ConfigurationTest</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Autowired</span>    <span class="token keyword">private</span> String msg<span class="token punctuation">;</span>    <span class="token annotation punctuation">@Test</span>    <span class="token keyword">void</span> <span class="token function">testConfiguration</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>msg<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>到这里就通过@Import属性实现了基于开发环境的配置基础上，对配置进行测试环境的追加操作。这样我们就可以实现每一个不同的测试用例加载不同的bean的效果，丰富测试用例的编写，同时不影响开发环境的配置。</p><p><strong>总结</strong></p><p>定义测试环境专用的配置类，然后通过@Import注解在具体的测试中导入临时的配置，例如测试用例，方便测试过程，且上述配置不影响其他的测试类环境</p><h3 id="③Web环境模拟测试"><a href="#③Web环境模拟测试" class="headerlink" title="③Web环境模拟测试"></a>③Web环境模拟测试</h3><blockquote><p>当前我们已经可以实现业务层和数据层的测试，并且通过临时配置，控制每个测试用例加载不同的测试数据。但是实际企业开发不仅要保障业务层与数据层的功能安全有效，也要保障控制层的功能正常。那么如何对控制层进行测试呢？</p></blockquote><p>对控制层功能进行测试有三个要点：</p><ul><li>1.运行测试程序时，必须启动web环境，不然没法测试web功能。</li><li>2.必须在测试程序中具备发送web请求的能力，不然无法实现web功能的测试。</li><li>3.测试过程必须出现预计值与真实值的比对结果才能确认测试结果是否通过。</li></ul><p>因此测试控制层接口这项工作就转换成了三件事</p><ul><li>1.如何在测试类中启动web测试</li><li>2.如何在测试类中发送web请求</li><li>3.如何在测试类进行请求结果比对</li></ul><p><strong>测试类中启动web环境</strong></p><p>@SpringBootTest注解带有一个属性，叫做webEnvironment。通过该属性就可以设置在测试用例中启动web环境，具体如下：</p><pre class="line-numbers language-JAVA"><code class="language-JAVA">@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)public class WebTest {}<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>可以指定启动的Web环境对应的端口，springboot提供了4种设置值，分别如下：</p><ul><li>MOCK：根据当前设置确认是否启动web环境，例如使用了Servlet的API就启动web环境，属于适配性的配置</li><li>DEFINED_PORT：使用自定义的端口作为web服务器端口</li><li>RANDOM_PORT：使用随机端口作为web服务器端口</li><li>NONE：不启动web环境</li></ul><p>建议大家测试时使用RANDOM_PORT，避免代码中因为写死设定引发线上功能打包测试时由于端口冲突导致意外现象的出现。</p><p><strong>测试类中发送请求</strong></p><p><strong>步骤①</strong>：在测试类中开启web虚拟调用功能，通过注解@AutoConfigureMockMvc实现此功能的开启</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@SpringBootTest</span><span class="token punctuation">(</span>webEnvironment <span class="token operator">=</span> SpringBootTest<span class="token punctuation">.</span>WebEnvironment<span class="token punctuation">.</span>RANDOM_PORT<span class="token punctuation">)</span><span class="token annotation punctuation">@AutoConfigureMockMvc</span> <span class="token comment" spellcheck="true">//开启虚拟MVC调用</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">WebTest</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤②</strong>：定义发起虚拟调用的对象MockMVC，通过自动装配的形式初始化对象</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@SpringBootTest</span><span class="token punctuation">(</span>webEnvironment <span class="token operator">=</span> SpringBootTest<span class="token punctuation">.</span>WebEnvironment<span class="token punctuation">.</span>RANDOM_PORT<span class="token punctuation">)</span><span class="token annotation punctuation">@AutoConfigureMockMvc</span> <span class="token comment" spellcheck="true">//开启虚拟MVC调用</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">WebTest</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Test</span>    <span class="token keyword">void</span> <span class="token function">testWeb</span><span class="token punctuation">(</span><span class="token annotation punctuation">@Autowired</span> MockMvc mvc<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤③</strong>：创建一个虚拟请求对象，封装请求的路径，并使用MockMVC对象发送对应请求</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@SpringBootTest</span><span class="token punctuation">(</span>webEnvironment <span class="token operator">=</span> SpringBootTest<span class="token punctuation">.</span>WebEnvironment<span class="token punctuation">.</span>RANDOM_PORT<span class="token punctuation">)</span><span class="token annotation punctuation">@AutoConfigureMockMvc</span> <span class="token comment" spellcheck="true">//开启虚拟MVC调用</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">WebTest</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Test</span>    <span class="token keyword">void</span> <span class="token function">testWeb</span><span class="token punctuation">(</span><span class="token annotation punctuation">@Autowired</span> MockMvc mvc<span class="token punctuation">)</span> <span class="token keyword">throws</span> Exception <span class="token punctuation">{</span>         <span class="token comment" spellcheck="true">//http://localhost:8080/books</span>        <span class="token comment" spellcheck="true">//创建虚拟请求，当前访问/books</span>        MockHttpServletRequestBuilder builder <span class="token operator">=</span> MockMvcRequestBuilders<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">"/books"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">//执行对应的请求</span>        mvc<span class="token punctuation">.</span><span class="token function">perform</span><span class="token punctuation">(</span>builder<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>web环境请求结果比对</strong></p><ul><li><p>响应状态匹配</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Test</span><span class="token keyword">void</span> <span class="token function">testStatus</span><span class="token punctuation">(</span><span class="token annotation punctuation">@Autowired</span> MockMvc mvc<span class="token punctuation">)</span> <span class="token keyword">throws</span> Exception <span class="token punctuation">{</span>    MockHttpServletRequestBuilder builder <span class="token operator">=</span> MockMvcRequestBuilders<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">"/books"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    ResultActions action <span class="token operator">=</span> mvc<span class="token punctuation">.</span><span class="token function">perform</span><span class="token punctuation">(</span>builder<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">//设定预期值 与真实值进行比较，成功测试通过，失败测试失败</span>    <span class="token comment" spellcheck="true">//定义本次调用的预期值</span>    StatusResultMatchers status <span class="token operator">=</span> MockMvcResultMatchers<span class="token punctuation">.</span><span class="token function">status</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    ResultMatcher ok <span class="token operator">=</span> status<span class="token punctuation">.</span><span class="token function">isOk</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//预计本次调用时成功的：状态200</span>    <span class="token comment" spellcheck="true">//添加预计值到本次调用过程中进行匹配</span>    action<span class="token punctuation">.</span><span class="token function">andExpect</span><span class="token punctuation">(</span>ok<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>响应体匹配（非json数据格式）</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Test</span><span class="token keyword">void</span> <span class="token function">testBody</span><span class="token punctuation">(</span><span class="token annotation punctuation">@Autowired</span> MockMvc mvc<span class="token punctuation">)</span> <span class="token keyword">throws</span> Exception <span class="token punctuation">{</span>    MockHttpServletRequestBuilder builder <span class="token operator">=</span> MockMvcRequestBuilders<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">"/books"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    ResultActions action <span class="token operator">=</span> mvc<span class="token punctuation">.</span><span class="token function">perform</span><span class="token punctuation">(</span>builder<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">//设定预期值 与真实值进行比较，成功测试通过，失败测试失败</span>    <span class="token comment" spellcheck="true">//定义本次调用的预期值</span>    ContentResultMatchers content <span class="token operator">=</span> MockMvcResultMatchers<span class="token punctuation">.</span><span class="token function">content</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    ResultMatcher result <span class="token operator">=</span> content<span class="token punctuation">.</span><span class="token function">string</span><span class="token punctuation">(</span><span class="token string">"springboot2"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">//添加预计值到本次调用过程中进行匹配</span>    action<span class="token punctuation">.</span><span class="token function">andExpect</span><span class="token punctuation">(</span>result<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>响应体匹配（json数据格式，开发中的主流使用方式）</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Test</span><span class="token keyword">void</span> <span class="token function">testJson</span><span class="token punctuation">(</span><span class="token annotation punctuation">@Autowired</span> MockMvc mvc<span class="token punctuation">)</span> <span class="token keyword">throws</span> Exception <span class="token punctuation">{</span>    MockHttpServletRequestBuilder builder <span class="token operator">=</span> MockMvcRequestBuilders<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">"/books"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    ResultActions action <span class="token operator">=</span> mvc<span class="token punctuation">.</span><span class="token function">perform</span><span class="token punctuation">(</span>builder<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">//设定预期值 与真实值进行比较，成功测试通过，失败测试失败</span>    <span class="token comment" spellcheck="true">//定义本次调用的预期值</span>    ContentResultMatchers content <span class="token operator">=</span> MockMvcResultMatchers<span class="token punctuation">.</span><span class="token function">content</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    ResultMatcher result <span class="token operator">=</span> content<span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span><span class="token string">"{\"id\":1,\"name\":\"springboot2\",\"type\":\"springboot\"}"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">//添加预计值到本次调用过程中进行匹配</span>    action<span class="token punctuation">.</span><span class="token function">andExpect</span><span class="token punctuation">(</span>result<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>响应头信息匹配</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Test</span><span class="token keyword">void</span> <span class="token function">testContentType</span><span class="token punctuation">(</span><span class="token annotation punctuation">@Autowired</span> MockMvc mvc<span class="token punctuation">)</span> <span class="token keyword">throws</span> Exception <span class="token punctuation">{</span>    MockHttpServletRequestBuilder builder <span class="token operator">=</span> MockMvcRequestBuilders<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">"/books"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    ResultActions action <span class="token operator">=</span> mvc<span class="token punctuation">.</span><span class="token function">perform</span><span class="token punctuation">(</span>builder<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">//设定预期值 与真实值进行比较，成功测试通过，失败测试失败</span>    <span class="token comment" spellcheck="true">//定义本次调用的预期值</span>    HeaderResultMatchers header <span class="token operator">=</span> MockMvcResultMatchers<span class="token punctuation">.</span><span class="token function">header</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    ResultMatcher contentType <span class="token operator">=</span> header<span class="token punctuation">.</span><span class="token function">string</span><span class="token punctuation">(</span><span class="token string">"Content-Type"</span><span class="token punctuation">,</span> <span class="token string">"application/json"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">//添加预计值到本次调用过程中进行匹配</span>    action<span class="token punctuation">.</span><span class="token function">andExpect</span><span class="token punctuation">(</span>contentType<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li></ul><p>头信息，正文信息，状态信息都有了，就可以组合出一个完美的响应结果比对结果了。以下范例就是三种信息同时进行匹配校验，也是一个完整的信息匹配过程。</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Test</span><span class="token keyword">void</span> <span class="token function">testGetById</span><span class="token punctuation">(</span><span class="token annotation punctuation">@Autowired</span> MockMvc mvc<span class="token punctuation">)</span> <span class="token keyword">throws</span> Exception <span class="token punctuation">{</span>    MockHttpServletRequestBuilder builder <span class="token operator">=</span> MockMvcRequestBuilders<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">"/books"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    ResultActions action <span class="token operator">=</span> mvc<span class="token punctuation">.</span><span class="token function">perform</span><span class="token punctuation">(</span>builder<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">//比状态信息</span>    StatusResultMatchers status <span class="token operator">=</span> MockMvcResultMatchers<span class="token punctuation">.</span><span class="token function">status</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    ResultMatcher ok <span class="token operator">=</span> status<span class="token punctuation">.</span><span class="token function">isOk</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    action<span class="token punctuation">.</span><span class="token function">andExpect</span><span class="token punctuation">(</span>ok<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">//比对头信息</span>    HeaderResultMatchers header <span class="token operator">=</span> MockMvcResultMatchers<span class="token punctuation">.</span><span class="token function">header</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    ResultMatcher contentType <span class="token operator">=</span> header<span class="token punctuation">.</span><span class="token function">string</span><span class="token punctuation">(</span><span class="token string">"Content-Type"</span><span class="token punctuation">,</span> <span class="token string">"application/json"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    action<span class="token punctuation">.</span><span class="token function">andExpect</span><span class="token punctuation">(</span>contentType<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">//比正文信息</span>    ContentResultMatchers content <span class="token operator">=</span> MockMvcResultMatchers<span class="token punctuation">.</span><span class="token function">content</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    ResultMatcher result <span class="token operator">=</span> content<span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span><span class="token string">"{\"id\":1,\"name\":\"springboot\",\"type\":\"springboot\"}"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    action<span class="token punctuation">.</span><span class="token function">andExpect</span><span class="token punctuation">(</span>result<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>总结</strong></p><ol><li>在测试类中测试web层接口要保障测试类启动时启动web容器，使用@SpringBootTest注解的webEnvironment属性可以虚拟web环境用于测试</li><li>为测试方法注入MockMvc对象，通过MockMvc对象可以发送虚拟请求，模拟web请求调用过程</li><li>web虚拟调用可以对本地虚拟请求的返回响应信息进行比对，分为响应头信息比对、响应体信息比对、响应状态信息比对</li></ol><h3 id="④数据层测试回滚"><a href="#④数据层测试回滚" class="headerlink" title="④数据层测试回滚"></a>④数据层测试回滚</h3><blockquote><p>测试用例开发完成后，在打包的阶段由于test生命周期属于必须被运行的生命周期，如果跳过会给系统带来极高的安全隐患，所以测试用例必须执行。但是新的问题就呈现了，测试用例如果测试时产生了事务提交就会在测试过程中对数据库数据产生影响，进而产生垃圾数据。这个过程不是我们希望发生的，</p><p>作为开发者希望测试用例正常运行，但是过程中产生的数据不要在我的系统中留痕，这样该如何处理呢？</p></blockquote><p>springboot早就为开发者想到了这个问题，并且针对此问题给出了最简解决方案，在原始测试用例中添加注解<code>@Transactional</code>即可实现当前测试用例的事务不提交。当程序运行后，springboot就会认为这是一个测试程序，无需提交事务，所以也就可以避免事务的提交。</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@SpringBootTest</span><span class="token annotation punctuation">@Transactional</span><span class="token annotation punctuation">@Rollback</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">DaoTest</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Autowired</span>    <span class="token keyword">private</span> BookService bookService<span class="token punctuation">;</span>    <span class="token annotation punctuation">@Test</span>    <span class="token keyword">void</span> <span class="token function">testSave</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>        Book book <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Book</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        book<span class="token punctuation">.</span><span class="token function">setName</span><span class="token punctuation">(</span><span class="token string">"springboot3"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        book<span class="token punctuation">.</span><span class="token function">setType</span><span class="token punctuation">(</span><span class="token string">"springboot3"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        book<span class="token punctuation">.</span><span class="token function">setDescription</span><span class="token punctuation">(</span><span class="token string">"springboot3"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        bookService<span class="token punctuation">.</span><span class="token function">save</span><span class="token punctuation">(</span>book<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>如果开发者想提交事务，也可以，再添加一个@RollBack的注解，设置回滚状态为false「<code>@RollBack(false)</code>」即可正常提交事务。</p><p><strong>总结</strong></p><ol><li>在springboot的测试类中通过添加注解@Transactional来阻止测试用例提交事务</li><li>通过注解@Rollback控制springboot测试类执行结果是否提交事务，需要配合注解@Transactional使用</li></ol><h3 id="⑤测试用例数据设定"><a href="#⑤测试用例数据设定" class="headerlink" title="⑤测试用例数据设定"></a>⑤测试用例数据设定</h3><p>测试用例的数据如果固定书写肯定是不合理的，springboot提供了在配置中使用随机值的机制，确保每次运行程序加载的数据都是随机的。具体如下：</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">testcase</span><span class="token punctuation">:</span>  <span class="token key atrule">book</span><span class="token punctuation">:</span>    <span class="token key atrule">id</span><span class="token punctuation">:</span> $<span class="token punctuation">{</span>random.int<span class="token punctuation">}</span>           <span class="token comment" spellcheck="true"># 随机整数</span>    <span class="token key atrule">id2</span><span class="token punctuation">:</span> $<span class="token punctuation">{</span>random.int(10)<span class="token punctuation">}</span>      <span class="token comment" spellcheck="true"># 10以内随机整数</span>    <span class="token key atrule">type</span><span class="token punctuation">:</span> $<span class="token punctuation">{</span>random.int(5<span class="token punctuation">,</span>10)<span class="token punctuation">}</span>   <span class="token comment" spellcheck="true">#  10到20随机整数</span>    <span class="token key atrule">name</span><span class="token punctuation">:</span> $<span class="token punctuation">{</span>random.value<span class="token punctuation">}</span>       <span class="token comment" spellcheck="true">#  随机字符串，MD5字符，32位</span>    <span class="token key atrule">uuid</span><span class="token punctuation">:</span> $<span class="token punctuation">{</span>random.uuid<span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">#  随机uuid</span>    <span class="token key atrule">publishTime</span><span class="token punctuation">:</span> $<span class="token punctuation">{</span>random.long<span class="token punctuation">}</span> <span class="token comment" spellcheck="true">#  随机整数（long范围）</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>当前配置就可以在每次运行程序时创建一组随机数据，避免每次运行时数据都是固定值的尴尬现象发生，有助于测试功能的进行。数据的加载按照之前加载数据的形式，使用@ConfigurationProperties注解即可</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Component</span><span class="token annotation punctuation">@Data</span><span class="token annotation punctuation">@ConfigurationProperties</span><span class="token punctuation">(</span>prefix <span class="token operator">=</span> <span class="token string">"testcase.book"</span><span class="token punctuation">)</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">BookCase</span> <span class="token punctuation">{</span>    <span class="token keyword">private</span> <span class="token keyword">int</span> id<span class="token punctuation">;</span>    <span class="token keyword">private</span> <span class="token keyword">int</span> id2<span class="token punctuation">;</span>    <span class="token keyword">private</span> <span class="token keyword">int</span> type<span class="token punctuation">;</span>    <span class="token keyword">private</span> String name<span class="token punctuation">;</span>    <span class="token keyword">private</span> String uuid<span class="token punctuation">;</span>    <span class="token keyword">private</span> <span class="token keyword">long</span> publishTime<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>总结</strong></p><p>使用随机数据可以替换测试用例中书写的固定数据，提高测试用例中的测试数据有效性</p><h2 id="4-内置数据层"><a href="#4-内置数据层" class="headerlink" title="4.内置数据层"></a>4.内置数据层</h2><p>基础篇中用到的数据层解决方案是 Mysql+Druid+MyBatisPlus。而三个技术分别对应了数据层操作的三个层面：</p><ul><li>数据源技术：Druid</li><li>持久化技术：MyBatisPlus</li><li>数据库技术：MySQL</li></ul><p>那么 SpringBoot 有内置的数据解决方案吗？</p><h3 id="①数据源技术"><a href="#①数据源技术" class="headerlink" title="①数据源技术"></a>①数据源技术</h3><p>springboot提供了3款内嵌数据源技术，分别如下：</p><ul><li><p>HikariCP</p><ul><li>springboot官方推荐的数据源技术，作为默认内置数据源使用。</li></ul></li><li><p>Tomcat提供DataSource</p><ul><li>Tomcat提供的DataSource，如果不想用HikartCP，并且使用tomcat作为web服务器进行web程序的开发，使用这个。使用时把HikartCP技术的坐标排除掉就OK了。</li></ul></li><li><p>Commons DBCP</p><ul><li>既不使用HikartCP也不使用tomcat的DataSource时，默认给你用这个。</li></ul></li></ul><p>我们之前配置druid时使用druid的starter对应的配置如下：</p><pre class="line-numbers language-YAML"><code class="language-YAML">spring:  datasource:    druid:         url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC      driver-class-name: com.mysql.cj.jdbc.Driver      username: root      password: root<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>换成是默认的数据源HikariCP后，直接吧druid删掉就行了，如下：</p><pre class="line-numbers language-YAML"><code class="language-YAML">spring:  datasource:    url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC    driver-class-name: com.mysql.cj.jdbc.Driver    username: root    password: root<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>当然，也可以写上是对hikari做的配置，但是url地址要单独配置，如下：</p><pre class="line-numbers language-YAML"><code class="language-YAML">spring:  datasource:    url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC    hikari:      driver-class-name: com.mysql.cj.jdbc.Driver      username: root      password: root<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>如果想对hikari做进一步的配置，可以继续配置其独立的属性。例如：</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">spring</span><span class="token punctuation">:</span>  <span class="token key atrule">datasource</span><span class="token punctuation">:</span>    <span class="token key atrule">url</span><span class="token punctuation">:</span> jdbc<span class="token punctuation">:</span>mysql<span class="token punctuation">:</span>//localhost<span class="token punctuation">:</span>3306/ssm_db<span class="token punctuation">?</span>serverTimezone=UTC    <span class="token key atrule">hikari</span><span class="token punctuation">:</span>      <span class="token key atrule">driver-class-name</span><span class="token punctuation">:</span> com.mysql.cj.jdbc.Driver      <span class="token key atrule">username</span><span class="token punctuation">:</span> root      <span class="token key atrule">password</span><span class="token punctuation">:</span> root      <span class="token key atrule">maximum-pool-size</span><span class="token punctuation">:</span> <span class="token number">50</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>如果不想使用hikari数据源，使用tomcat的数据源或者DBCP配置格式也是一样的。学习到这里，以后我们做数据层时，数据源对象的选择就不再是单一的使用druid数据源技术了，可以根据需要自行选择。</p><h3 id="②持久化技术"><a href="#②持久化技术" class="headerlink" title="②持久化技术"></a>②持久化技术</h3><p>springboot提供了一套现成的数据层技术，叫做JdbcTemplate。</p><p><strong>步骤①</strong>：导入jdbc对应的starter</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework.boot<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-boot-starter-jdbc<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>&lt;/dependency<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤②</strong>：自动装配JdbcTemplate对象</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@SpringBootTest</span><span class="token keyword">class</span> <span class="token class-name">Springboot15SqlApplicationTests</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Test</span>    <span class="token keyword">void</span> <span class="token function">testJdbcTemplate</span><span class="token punctuation">(</span><span class="token annotation punctuation">@Autowired</span> JdbcTemplate jdbcTemplate<span class="token punctuation">)</span><span class="token punctuation">{</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤③</strong>：使用JdbcTemplate实现查询操作（非实体类封装数据的查询操作）</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Test</span><span class="token keyword">void</span> <span class="token function">testJdbcTemplate</span><span class="token punctuation">(</span><span class="token annotation punctuation">@Autowired</span> JdbcTemplate jdbcTemplate<span class="token punctuation">)</span><span class="token punctuation">{</span>    String sql <span class="token operator">=</span> <span class="token string">"select * from tbl_book"</span><span class="token punctuation">;</span>    List<span class="token operator">&lt;</span>Map<span class="token operator">&lt;</span>String<span class="token punctuation">,</span> Object<span class="token operator">>></span> maps <span class="token operator">=</span> jdbcTemplate<span class="token punctuation">.</span><span class="token function">queryForList</span><span class="token punctuation">(</span>sql<span class="token punctuation">)</span><span class="token punctuation">;</span>    System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>maps<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤④</strong>：使用JdbcTemplate实现查询操作（实体类封装数据的查询操作）</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Test</span><span class="token keyword">void</span> <span class="token function">testJdbcTemplate</span><span class="token punctuation">(</span><span class="token annotation punctuation">@Autowired</span> JdbcTemplate jdbcTemplate<span class="token punctuation">)</span><span class="token punctuation">{</span>    String sql <span class="token operator">=</span> <span class="token string">"select * from tbl_book"</span><span class="token punctuation">;</span>    RowMapper<span class="token operator">&lt;</span>Book<span class="token operator">></span> rm <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">RowMapper</span><span class="token operator">&lt;</span>Book<span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token annotation punctuation">@Override</span>        <span class="token keyword">public</span> Book <span class="token function">mapRow</span><span class="token punctuation">(</span>ResultSet rs<span class="token punctuation">,</span> <span class="token keyword">int</span> rowNum<span class="token punctuation">)</span> <span class="token keyword">throws</span> SQLException <span class="token punctuation">{</span>            Book book <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Book</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            book<span class="token punctuation">.</span><span class="token function">setId</span><span class="token punctuation">(</span>rs<span class="token punctuation">.</span><span class="token function">getInt</span><span class="token punctuation">(</span><span class="token string">"id"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            book<span class="token punctuation">.</span><span class="token function">setName</span><span class="token punctuation">(</span>rs<span class="token punctuation">.</span><span class="token function">getString</span><span class="token punctuation">(</span><span class="token string">"name"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            book<span class="token punctuation">.</span><span class="token function">setType</span><span class="token punctuation">(</span>rs<span class="token punctuation">.</span><span class="token function">getString</span><span class="token punctuation">(</span><span class="token string">"type"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            book<span class="token punctuation">.</span><span class="token function">setDescription</span><span class="token punctuation">(</span>rs<span class="token punctuation">.</span><span class="token function">getString</span><span class="token punctuation">(</span><span class="token string">"description"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">return</span> book<span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span><span class="token punctuation">;</span>    List<span class="token operator">&lt;</span>Book<span class="token operator">></span> list <span class="token operator">=</span> jdbcTemplate<span class="token punctuation">.</span><span class="token function">query</span><span class="token punctuation">(</span>sql<span class="token punctuation">,</span> rm<span class="token punctuation">)</span><span class="token punctuation">;</span>    System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>list<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤⑤</strong>：使用JdbcTemplate实现增删改操作</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Test</span><span class="token keyword">void</span> <span class="token function">testJdbcTemplateSave</span><span class="token punctuation">(</span><span class="token annotation punctuation">@Autowired</span> JdbcTemplate jdbcTemplate<span class="token punctuation">)</span><span class="token punctuation">{</span>    String sql <span class="token operator">=</span> <span class="token string">"insert into tbl_book values(3,'springboot1','springboot2','springboot3')"</span><span class="token punctuation">;</span>    jdbcTemplate<span class="token punctuation">.</span><span class="token function">update</span><span class="token punctuation">(</span>sql<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>如果想对JdbcTemplate对象进行相关配置，可以在yml文件中进行设定，具体如下：</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">spring</span><span class="token punctuation">:</span>  <span class="token key atrule">jdbc</span><span class="token punctuation">:</span>    <span class="token key atrule">template</span><span class="token punctuation">:</span>      <span class="token key atrule">query-timeout</span><span class="token punctuation">:</span> <span class="token number">-1   </span><span class="token comment" spellcheck="true"># 查询超时时间</span>      <span class="token key atrule">max-rows</span><span class="token punctuation">:</span> <span class="token number">500       </span><span class="token comment" spellcheck="true"># 最大行数</span>      <span class="token key atrule">fetch-size</span><span class="token punctuation">:</span> <span class="token number">-1      </span><span class="token comment" spellcheck="true"># 缓存行数</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="③数据库技术"><a href="#③数据库技术" class="headerlink" title="③数据库技术"></a>③数据库技术</h3><p>springboot提供了3款内置的数据库，分别是</p><ul><li>H2</li><li>HSQL</li><li>Derby</li></ul><p>我们一直使用MySQL数据库就挺好的，为什么有需求用这个呢？原因就在于这三个数据库都可以采用内嵌容器的形式运行，在应用程序运行后，如果我们进行测试工作，此时测试的数据无需存储在磁盘上，但是又要测试使用，内嵌数据库就方便了，运行在内存中，该测试测试，该运行运行，等服务器关闭后，一切烟消云散，多好，省得你维护外部数据库了。这也是内嵌数据库的最大优点，方便进行功能测试。三款数据库还可以独立安装。</p><p>下面以H2数据库为例讲解如何使用这些内嵌数据库，操作步骤也非常简单，简单才好用嘛</p><p><strong>步骤①</strong>：导入H2数据库对应的坐标，一共2个</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>com.h2database<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>h2<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework.boot<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-boot-starter-data-jpa<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤②</strong>：将工程设置为web工程，启动工程时启动H2数据库</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework.boot<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-boot-starter-web<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤③</strong>：通过配置开启H2数据库控制台访问程序，也可以使用其他的数据库连接软件操作</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">spring</span><span class="token punctuation">:</span>  <span class="token key atrule">h2</span><span class="token punctuation">:</span>    <span class="token key atrule">console</span><span class="token punctuation">:</span>      <span class="token key atrule">enabled</span><span class="token punctuation">:</span> <span class="token boolean important">true</span>      <span class="token key atrule">path</span><span class="token punctuation">:</span> /h2<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>web端访问路径&#x2F;h2，访问密码123456，如果访问失败，先配置下列数据源，启动程序运行后再次访问&#x2F;h2路径就可以正常访问了</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">datasource</span><span class="token punctuation">:</span>  <span class="token key atrule">url</span><span class="token punctuation">:</span> jdbc<span class="token punctuation">:</span>h2<span class="token punctuation">:</span>~/test  <span class="token key atrule">hikari</span><span class="token punctuation">:</span>    <span class="token key atrule">driver-class-name</span><span class="token punctuation">:</span> org.h2.Driver    <span class="token key atrule">username</span><span class="token punctuation">:</span> sa    <span class="token key atrule">password</span><span class="token punctuation">:</span> <span class="token number">123456</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤④</strong>：使用JdbcTemplate或MyBatisPlus技术操作数据库</p><p>其实我们只是换了一个数据库而已，其他的东西都不受影响。一个重要提醒，别忘了，上线时，把内存级数据库关闭，采用MySQL数据库，关闭方式就是设置enabled属性为false即可。</p><p><strong>总结</strong></p><p>现在的可选技术就丰富多了，开发程序时就可以在技术中任选一套数据库解决方案了。</p><ul><li>数据源技术：Druid、Hikari、tomcat DataSource、DBCP</li><li>持久化技术：MyBatisPlus、MyBatis、JdbcTemplate</li><li>数据库技术：MySQL、H2、HSQL、Derby</li></ul><h2 id="5-监控"><a href="#5-监控" class="headerlink" title="5.监控"></a>5.监控</h2><p><strong>监控的意义</strong></p><ul><li><p>监控服务状态是否处理宕机状态</p></li><li><p>监控服务运行指标</p></li><li><p>监控程序运行日志</p></li><li><p>管理服务状态</p></li></ul><h3 id="①可视化监控平台"><a href="#①可视化监控平台" class="headerlink" title="①可视化监控平台"></a>①可视化监控平台</h3><blockquote><p>Spring Boot Admin，这是一个开源社区项目，用于管理和监控SpringBoot应用程序。这个项目中包含有客户端和服务端两部分，而监控平台指的就是服务端。我们做的程序如果需要被监控，将我们做的程序制作成客户端，然后配置服务端地址后，服务端就可以通过HTTP请求的方式从客户端获取对应的信息，并通过UI界面展示对应信息。</p></blockquote><h4 id="服务端开发"><a href="#服务端开发" class="headerlink" title="服务端开发"></a><strong>服务端开发</strong></h4><p><strong>步骤①</strong>：导入spring-boot-admin对应的starter，版本与当前使用的springboot版本保持一致，并将其配置成web工程，也可通过创建项目时使用勾选的形式完成(Ops&#x2F;Codecentric’s Spring Boot Admin (Server))。</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>de.codecentric<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-boot-admin-starter-server<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>2.7.3<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework.boot<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-boot-starter-web<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤②</strong>：在引导类上添加注解@EnableAdminServer，声明当前应用启动后作为SpringBootAdmin的服务器使用</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@SpringBootApplication</span><span class="token annotation punctuation">@EnableAdminServer</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">SpringbootAdminServerApplication</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>        SpringApplication<span class="token punctuation">.</span><span class="token function">run</span><span class="token punctuation">(</span>SpringbootAdminServerApplication<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">,</span> args<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤③</strong>：运行程序，启动后就可以访问 <a href="http://127.0.0.1:8080/">http://127.0.0.1:8080</a></p><p><img src="https://img.jwt1399.top/img/202209141909968.png"></p><h4 id="客户端开发"><a href="#客户端开发" class="headerlink" title="客户端开发"></a><strong>客户端开发</strong></h4><p><strong>步骤①</strong>：导入springboot admin对应的starter，版本与当前使用的springboot版本保持一致，并将其配置成web工程</p><p>上述过程也可以通过创建项目时使用勾选的形式完成，不过一定要小心，端口配置成与服务端不一样的，否则会冲突。</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>de.codecentric<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-boot-admin-starter-client<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>2.5.4<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework.boot<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-boot-starter-web<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤②</strong>：设置当前客户端将信息上传到哪个服务器上，通过yml文件配置</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">spring</span><span class="token punctuation">:</span>  <span class="token key atrule">boot</span><span class="token punctuation">:</span>    <span class="token key atrule">admin</span><span class="token punctuation">:</span>      <span class="token key atrule">client</span><span class="token punctuation">:</span>        <span class="token key atrule">url</span><span class="token punctuation">:</span> http<span class="token punctuation">:</span>//localhost<span class="token punctuation">:</span><span class="token number">8080  </span><span class="token comment" spellcheck="true">#服务端url</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤③</strong>：运行程序，启动后再次访问服务端程序。</p><table><thead><tr><th><img src="https://img.jwt1399.top/img/202209142004101.png"></th><th><img src="https://img.jwt1399.top/img/202209142004655.png" /></th></tr></thead></table><p>当前监控了1个程序，点击进去查看详细信息。由于当前没有设置开放哪些信息给监控服务器，所以目前看不到什么有效的信息。下面需要做两组配置就可以看到信息了。</p><ol><li><p>开放指定信息给服务器看</p></li><li><p>允许服务器以HTTP请求的方式获取对应的信息</p></li></ol><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">spring</span><span class="token punctuation">:</span>  <span class="token key atrule">boot</span><span class="token punctuation">:</span>    <span class="token key atrule">admin</span><span class="token punctuation">:</span>      <span class="token key atrule">client</span><span class="token punctuation">:</span>        <span class="token key atrule">url</span><span class="token punctuation">:</span> http<span class="token punctuation">:</span>//localhost<span class="token punctuation">:</span><span class="token number">8080</span><span class="token key atrule">management</span><span class="token punctuation">:</span>  <span class="token key atrule">endpoint</span><span class="token punctuation">:</span>    <span class="token key atrule">health</span><span class="token punctuation">:</span>      <span class="token key atrule">show-details</span><span class="token punctuation">:</span> always <span class="token comment" spellcheck="true"># 开放所有的健康信息</span>  <span class="token key atrule">endpoints</span><span class="token punctuation">:</span>    <span class="token key atrule">web</span><span class="token punctuation">:</span>      <span class="token key atrule">exposure</span><span class="token punctuation">:</span>        <span class="token key atrule">include</span><span class="token punctuation">:</span> <span class="token string">"*"</span> <span class="token comment" spellcheck="true"># 使用*表示查阅全部。记得带引号</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>配置后再刷新服务器页面，就可以看到所有的信息了。</p><p><img src="https://img.jwt1399.top/img/202209142006376.png"></p><p>以上界面中展示的信息量就非常大了，包含了13组信息，有性能指标监控，加载的bean列表，加载的系统属性，日志的显示控制等等。<strong>还可以配置多个客户端</strong>，通过配置客户端的方式在其他的springboot程序中添加服务端坐标，这样当前服务器就可以监控多个客户端程序了。</p><p>进入监控面板，如果你加载的应用具有功能，在监控面板中可以看到3组信息展示的与之前加载的空工程不一样。</p><ul><li><p>类加载面板中可以查阅到开发者自定义的类</p></li><li><p>映射中可以查阅到当前应用配置的所有请求</p></li><li><p>性能指标中可以查阅当前应用独有的请求路径统计数据</p></li></ul><p><strong>总结</strong></p><ol><li>开发监控服务端需要导入坐标，然后在引导类上添加注解@EnableAdminServer，并将其配置成web程序即可</li><li>开发被监控的客户端需要导入坐标，然后配置服务端服务器地址，并做开放指标的设定即可</li><li>在监控平台中可以查阅到各种各样被监控的指标，前提是客户端开放了被监控的指标</li></ol><h3 id="②监控原理"><a href="#②监控原理" class="headerlink" title="②监控原理"></a>②监控原理</h3><p>通过查阅监控中的映射指标，可以看到当前系统中可以运行的所有请求路径，其中大部分路径以&#x2F;actuator开头</p><p><img src="https://img.jwt1399.top/img/202209142034874.png"></p><p>首先这些请求路径不是开发者自己编写的，其次这个路径代表什么含义呢？既然这个路径可以访问，就可以通过浏览器发送该请求看看究竟可以得到什么信息。</p><p><img src="https://img.jwt1399.top/img/202209142035573.png"></p><p>通过发送请求，可以得到上面json信息，其中每一组数据都有一个请求路径，而在这里请求路径中有之前看到过的health，发送此请求又得到了一组信息</p><pre class="line-numbers language-json"><code class="language-json"><span class="token punctuation">{</span>    <span class="token property">"status"</span><span class="token operator">:</span> <span class="token string">"UP"</span><span class="token punctuation">,</span>    <span class="token property">"components"</span><span class="token operator">:</span> <span class="token punctuation">{</span>        <span class="token property">"diskSpace"</span><span class="token operator">:</span> <span class="token punctuation">{</span>            <span class="token property">"status"</span><span class="token operator">:</span> <span class="token string">"UP"</span><span class="token punctuation">,</span>            <span class="token property">"details"</span><span class="token operator">:</span> <span class="token punctuation">{</span>                <span class="token property">"total"</span><span class="token operator">:</span> <span class="token number">297042808832</span><span class="token punctuation">,</span>                <span class="token property">"free"</span><span class="token operator">:</span> <span class="token number">72284409856</span><span class="token punctuation">,</span>                <span class="token property">"threshold"</span><span class="token operator">:</span> <span class="token number">10485760</span><span class="token punctuation">,</span>                <span class="token property">"exists"</span><span class="token operator">:</span> <span class="token boolean">true</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span><span class="token punctuation">,</span>        <span class="token property">"ping"</span><span class="token operator">:</span> <span class="token punctuation">{</span>            <span class="token property">"status"</span><span class="token operator">:</span> <span class="token string">"UP"</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>当前信息与监控面板中的数据存在着对应关系</p><img src="https://img.jwt1399.top/img/202209142038284.png" /><p>原来监控中显示的信息实际上是通过发送请求后得到json数据，然后展示出来。按照上述操作，可以发送更多的以&#x2F;actuator开头的链接地址，获取更多的数据，这些数据汇总到一起组成了监控平台显示的所有数据。</p><p>到这里我们得到了一个核心信息，监控平台中显示的信息实际上是通过对被监控的应用发送请求得到的。那这些请求谁开发的呢？打开被监控应用的pom文件，其中导入了springboot admin的对应的client，在这个资源中导入了一个名称叫做actuator的包。被监控的应用之所以可以对外提供上述请求路径，就是因为添加了这个包。</p><p><img src="https://img.jwt1399.top/img/202209142039889.png"></p><p>这个actuator是什么呢？这就是本节要讲的核心内容，监控的端点。</p><blockquote><p>Actuator，可以称为端点，描述了一组监控信息，SpringBootAdmin提供了多个内置端点，通过访问端点就可以获取对应的监控信息，也可以根据需要自定义端点信息。通过发送请求路劲**&#x2F;actuator<strong>可以访问应用所有端点信息，如果端点中还有明细信息可以发送请求</strong>&#x2F;actuator&#x2F;端点名称**来获取详细信息。</p></blockquote><table><thead><tr><th>ID</th><th>描述</th><th>默认启用</th></tr></thead><tbody><tr><td>auditevents</td><td>暴露当前应用程序的审计事件信息。</td><td>是</td></tr><tr><td>beans</td><td>显示应用程序中所有 Spring bean 的完整列表。</td><td>是</td></tr><tr><td>caches</td><td>暴露可用的缓存。</td><td>是</td></tr><tr><td>conditions</td><td>显示在配置和自动配置类上评估的条件以及它们匹配或不匹配的原因。</td><td>是</td></tr><tr><td>configprops</td><td>显示所有 @ConfigurationProperties 的校对清单。</td><td>是</td></tr><tr><td>env</td><td>暴露 Spring ConfigurableEnvironment 中的属性。</td><td>是</td></tr><tr><td>flyway</td><td>显示已应用的 Flyway 数据库迁移。</td><td>是</td></tr><tr><td>health</td><td>显示应用程序健康信息</td><td>是</td></tr><tr><td>httptrace</td><td>显示 HTTP 追踪信息（默认情况下，最后 100 个  HTTP 请求&#x2F;响应交换）。</td><td>是</td></tr><tr><td>info</td><td>显示应用程序信息。</td><td>是</td></tr><tr><td>integrationgraph</td><td>显示 Spring Integration 图。</td><td>是</td></tr><tr><td>loggers</td><td>显示和修改应用程序中日志记录器的配置。</td><td>是</td></tr><tr><td>liquibase</td><td>显示已应用的 Liquibase 数据库迁移。</td><td>是</td></tr><tr><td>metrics</td><td>显示当前应用程序的指标度量信息。</td><td>是</td></tr><tr><td>mappings</td><td>显示所有 @RequestMapping 路径的整理清单。</td><td>是</td></tr><tr><td>scheduledtasks</td><td>显示应用程序中的调度任务。</td><td>是</td></tr><tr><td>sessions</td><td>允许从 Spring Session 支持的会话存储中检索和删除用户会话。当使用 Spring Session 的响应式 Web 应用程序支持时不可用。</td><td>是</td></tr><tr><td>shutdown</td><td>正常关闭应用程序。</td><td>否</td></tr><tr><td>threaddump</td><td>执行线程 dump。</td><td>是</td></tr><tr><td>heapdump</td><td>返回一个 hprof 堆 dump 文件。</td><td>是</td></tr><tr><td>jolokia</td><td>通过 HTTP 暴露 JMX bean（当  Jolokia 在 classpath 上时，不适用于 WebFlux）。</td><td>是</td></tr><tr><td>logfile</td><td>返回日志文件的内容（如果已设置 logging.file 或 logging.path 属性）。支持使用 HTTP Range 头来检索部分日志文件的内容。</td><td>是</td></tr><tr><td>prometheus</td><td>以可以由 Prometheus 服务器抓取的格式暴露指标。</td><td>是</td></tr></tbody></table><p>上述端点每一项代表被监控的指标，如果对外开放则监控平台可以查询到对应的端点信息，如果未开放则无法查询对应的端点信息。通过配置可以设置端点是否对外开放功能。使用enable属性控制端点是否对外开放。其中health端点为默认端点，不能关闭。</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">management</span><span class="token punctuation">:</span>  <span class="token key atrule">endpoint</span><span class="token punctuation">:</span>    <span class="token key atrule">health</span><span class="token punctuation">:</span><span class="token comment" spellcheck="true"># 端点名称</span>      <span class="token key atrule">show-details</span><span class="token punctuation">:</span> always    <span class="token key atrule">info</span><span class="token punctuation">:</span>  <span class="token comment" spellcheck="true"># 端点名称</span>      <span class="token key atrule">enabled</span><span class="token punctuation">:</span> <span class="token boolean important">true</span><span class="token comment" spellcheck="true"># 是否开放</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>为了方便开发者快速配置端点，springboot admin设置了13个较为常用的端点作为默认开放的端点，如果需要控制默认开放的端点的开放状态，可以通过配置设置，如下：</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">management</span><span class="token punctuation">:</span>  <span class="token key atrule">endpoints</span><span class="token punctuation">:</span>    <span class="token key atrule">enabled-by-default</span><span class="token punctuation">:</span> <span class="token boolean important">true</span><span class="token comment" spellcheck="true"># 是否开启默认端点，默认值true</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>上述端点开启后，就可以通过端点对应的路径查看对应的信息了。但是此时还不能通过HTTP请求查询此信息，还需要开启通过HTTP请求查询的端点名称，使用“*”可以简化配置成开放所有端点的WEB端HTTP请求权限。</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">management</span><span class="token punctuation">:</span>  <span class="token key atrule">endpoints</span><span class="token punctuation">:</span>    <span class="token key atrule">web</span><span class="token punctuation">:</span>      <span class="token key atrule">exposure</span><span class="token punctuation">:</span>        <span class="token key atrule">include</span><span class="token punctuation">:</span> <span class="token string">"*"</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>整体上来说，对于端点的配置有两组信息，一组是endpoints开头的，对所有端点进行配置，一组是endpoint开头的，对具体端点进行配置。</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">management</span><span class="token punctuation">:</span>  <span class="token key atrule">endpoint</span><span class="token punctuation">:</span><span class="token comment" spellcheck="true"># 具体端点的配置</span>    <span class="token key atrule">health</span><span class="token punctuation">:</span>      <span class="token key atrule">show-details</span><span class="token punctuation">:</span> always    <span class="token key atrule">info</span><span class="token punctuation">:</span>      <span class="token key atrule">enabled</span><span class="token punctuation">:</span> <span class="token boolean important">true</span>  <span class="token key atrule">endpoints</span><span class="token punctuation">:</span><span class="token comment" spellcheck="true"># 全部端点的配置</span>    <span class="token key atrule">web</span><span class="token punctuation">:</span>      <span class="token key atrule">exposure</span><span class="token punctuation">:</span>        <span class="token key atrule">include</span><span class="token punctuation">:</span> <span class="token string">"*"</span>    <span class="token key atrule">enabled-by-default</span><span class="token punctuation">:</span> <span class="token boolean important">true</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>总结</strong></p><ol><li><p>被监控客户端通过添加actuator的坐标可以对外提供被访问的端点功能</p></li><li><p>端点功能的开放与关闭可以通过配置进行控制</p></li><li><p>web端默认无法获取所有端点信息，通过配置开放端点功能</p></li></ol><h3 id="③自定义监控指标"><a href="#③自定义监控指标" class="headerlink" title="③自定义监控指标"></a>③自定义监控指标</h3><p>端点描述了被监控的信息，除了系统默认的指标，还可以自行添加显示的指标，下面就通过3种不同的端点的指标自定义方式来学习端点信息的二次开发。</p><h4 id="INFO端点"><a href="#INFO端点" class="headerlink" title="INFO端点"></a><strong>INFO端点</strong></h4><p>info端点描述了当前应用的基本信息，可以通过两种形式快速配置info端点的信息</p><ul><li><p>配置形式</p><p>在yml文件中通过设置info节点的信息就可以快速配置端点信息</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">management</span><span class="token punctuation">:</span>  <span class="token key atrule">info</span><span class="token punctuation">:</span>    <span class="token key atrule">env</span><span class="token punctuation">:</span>      <span class="token key atrule">enabled</span><span class="token punctuation">:</span> <span class="token boolean important">true</span><span class="token key atrule">info</span><span class="token punctuation">:</span>  <span class="token key atrule">appName</span><span class="token punctuation">:</span> @project.artifactId@  <span class="token key atrule">version</span><span class="token punctuation">:</span> @project.version@  <span class="token key atrule">company</span><span class="token punctuation">:</span> jianjian  <span class="token key atrule">author</span><span class="token punctuation">:</span> xiaojian<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>配置完毕后，对应信息显示在监控平台上</p></li></ul><p><img src="https://img.jwt1399.top/img/202209142112404.png"></p><ul><li><p>编程形式</p><p>通过配置的形式只能添加固定的数据，如果需要动态数据还可以通过配置bean的方式为info端点添加信息，此信息与配置信息共存</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Component</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">InfoConfig</span> <span class="token keyword">implements</span> <span class="token class-name">InfoContributor</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">contribute</span><span class="token punctuation">(</span>Info<span class="token punctuation">.</span>Builder builder<span class="token punctuation">)</span> <span class="token punctuation">{</span>        builder<span class="token punctuation">.</span><span class="token function">withDetail</span><span class="token punctuation">(</span><span class="token string">"runTime"</span><span class="token punctuation">,</span>System<span class="token punctuation">.</span><span class="token function">currentTimeMillis</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//添加单个信息</span>        Map infoMap <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">HashMap</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        infoMap<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token string">"buildTime"</span><span class="token punctuation">,</span><span class="token string">"2006"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        builder<span class="token punctuation">.</span><span class="token function">withDetails</span><span class="token punctuation">(</span>infoMap<span class="token punctuation">)</span><span class="token punctuation">;</span>              <span class="token comment" spellcheck="true">//添加一组信息</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li></ul><h4 id="Health端点"><a href="#Health端点" class="headerlink" title="Health端点"></a><strong>Health端点</strong></h4><p>health端点描述当前应用的运行健康指标，即应用的运行是否成功。通过编程的形式可以扩展指标信息。</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Component</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">HealthConfig</span> <span class="token keyword">extends</span> <span class="token class-name">AbstractHealthIndicator</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">protected</span> <span class="token keyword">void</span> <span class="token function">doHealthCheck</span><span class="token punctuation">(</span>Health<span class="token punctuation">.</span>Builder builder<span class="token punctuation">)</span> <span class="token keyword">throws</span> Exception <span class="token punctuation">{</span>        <span class="token keyword">boolean</span> condition <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>condition<span class="token punctuation">)</span> <span class="token punctuation">{</span>            builder<span class="token punctuation">.</span><span class="token function">status</span><span class="token punctuation">(</span>Status<span class="token punctuation">.</span>UP<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//设置运行状态为启动状态</span>            builder<span class="token punctuation">.</span><span class="token function">withDetail</span><span class="token punctuation">(</span><span class="token string">"runTime"</span><span class="token punctuation">,</span> System<span class="token punctuation">.</span><span class="token function">currentTimeMillis</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            Map infoMap <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">HashMap</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            infoMap<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token string">"buildTime"</span><span class="token punctuation">,</span> <span class="token string">"2006"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            builder<span class="token punctuation">.</span><span class="token function">withDetails</span><span class="token punctuation">(</span>infoMap<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span><span class="token keyword">else</span><span class="token punctuation">{</span>            builder<span class="token punctuation">.</span><span class="token function">status</span><span class="token punctuation">(</span>Status<span class="token punctuation">.</span>OUT_OF_SERVICE<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//设置运行状态为不在服务状态</span>            builder<span class="token punctuation">.</span><span class="token function">withDetail</span><span class="token punctuation">(</span><span class="token string">"上线了吗？"</span><span class="token punctuation">,</span><span class="token string">"你做梦"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>当任意一个组件状态不为UP时，整体应用对外服务状态为非UP状态。</p><p><img src="https://img.jwt1399.top/img/202209142124604.png"></p><h4 id="Metrics端点"><a href="#Metrics端点" class="headerlink" title="Metrics端点"></a><strong>Metrics端点</strong></h4><p>metrics端点描述了性能指标，除了系统自带的监控性能指标，还可以自定义性能指标。</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Service</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">BookServiceImpl</span> <span class="token keyword">extends</span> <span class="token class-name">ServiceImpl</span><span class="token operator">&lt;</span>BookDao<span class="token punctuation">,</span> Book<span class="token operator">></span> <span class="token keyword">implements</span> <span class="token class-name">IBookService</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Autowired</span>    <span class="token keyword">private</span> BookDao bookDao<span class="token punctuation">;</span>    <span class="token keyword">private</span> Counter counter<span class="token punctuation">;</span>    <span class="token keyword">public</span> <span class="token function">BookServiceImpl</span><span class="token punctuation">(</span>MeterRegistry meterRegistry<span class="token punctuation">)</span><span class="token punctuation">{</span>        counter <span class="token operator">=</span> meterRegistry<span class="token punctuation">.</span><span class="token function">counter</span><span class="token punctuation">(</span><span class="token string">"用户付费操作次数："</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> <span class="token keyword">boolean</span> <span class="token function">delete</span><span class="token punctuation">(</span>Integer id<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">//每次执行删除业务等同于执行了付费业务</span>        counter<span class="token punctuation">.</span><span class="token function">increment</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> bookDao<span class="token punctuation">.</span><span class="token function">deleteById</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span> <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>在性能指标中就出现了自定义的性能指标监控项</p><p><img src="https://img.jwt1399.top/img/202209142118617.png"></p><p><strong>自定义端点</strong></p><p>可以根据业务需要自定义端点，方便业务监控</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Component</span><span class="token annotation punctuation">@Endpoint</span><span class="token punctuation">(</span>id<span class="token operator">=</span><span class="token string">"pay"</span><span class="token punctuation">,</span>enableByDefault <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">)</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">PayEndpoint</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@ReadOperation</span>    <span class="token keyword">public</span> Object <span class="token function">getPay</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>        Map payMap <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">HashMap</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        payMap<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token string">"level 1"</span><span class="token punctuation">,</span><span class="token string">"300"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        payMap<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token string">"level 2"</span><span class="token punctuation">,</span><span class="token string">"291"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        payMap<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token string">"level 3"</span><span class="token punctuation">,</span><span class="token string">"666"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> payMap<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>由于此端点数据spirng boot admin无法预知该如何展示，所以通过界面无法看到此数据，通过HTTP请求路径可以获取到当前端点的信息，但是需要先开启当前端点对外功能，或者设置当前端点为默认开发的端点。</p><p><img src="https://img.jwt1399.top/img/202209142123262.png"></p><p><strong>总结</strong></p><ol><li>端点的指标可以自定义，但是每种不同的指标根据其功能不同，自定义方式不同</li><li>info端点通过配置和编程的方式都可以添加端点指标</li><li>health端点通过编程的方式添加端点指标，需要注意要为对应指标添加启动状态的逻辑设定</li><li>metrics指标通过在业务中添加监控操作设置指标</li><li>可以自定义端点添加更多的指标</li></ol><h1 id="Sponsor❤️"><a href="#Sponsor❤️" class="headerlink" title="Sponsor❤️"></a>Sponsor❤️</h1><p>您的支持是我不断前进的动力，如果您感觉本文对您有所帮助的话，可以考虑打赏一下本文，用以维持本博客的运营费用，拒绝白嫖，从你我做起！🥰🥰🥰</p><table>  <tbody>     <tr>         <td style="text-align:center;">支付宝</td>         <td style="text-align:center;">微信</td>     </tr>   <tr>    <td style="text-align:center;" ><img width="200" src="https://jwt1399.top/medias/reward/alipay.png"></td>          <td style="text-align:center;"><img width="200" src="https://jwt1399.top/medias/reward/sponor_wechat.png"></td>       </tr></tbody></table>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;实用篇包含运维实用篇和开发实用篇，运维实用篇的定位是玩转配置；开发实用篇包含热部署、高级配置、测试进阶、内置数据层、监控等。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;小简从 0 开始学 Java 知识之 &lt;a</summary>
        
      
    
    
    
    <category term="Spring" scheme="https://jwt1399.top/categories/Spring/"/>
    
    
    <category term="SpringBoot" scheme="https://jwt1399.top/tags/SpringBoot/"/>
    
  </entry>
  
  <entry>
    <title>SpringBoot-基础篇</title>
    <link href="https://jwt1399.top/posts/33757.html"/>
    <id>https://jwt1399.top/posts/33757.html</id>
    <published>2022-09-06T06:04:27.000Z</published>
    <updated>2022-09-17T04:28:40.663Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>基础篇包含如何创建一个SpringBoot工程、SpringBoot的基础配置语法格式、常见实用技术整合、整合综合应用。</p></blockquote><p>小简从 0 开始学 Java 知识之 <a href="https://jwt1399.top/posts/29829.html">Java-学习路线</a> 中的《SpringBoot-基础篇》，不定期更新所学笔记，期待一年后的蜕变吧！&lt;有同样想法的小伙伴，可以联系我一起交流学习哦！&gt;</p><ul><li><p><input checked="" disabled="" type="checkbox"> 🚩时间安排：预计4天更新完</p></li><li><p><input checked="" disabled="" type="checkbox"> 🎯开始时间：09-06</p></li><li><p><input checked="" disabled="" type="checkbox"> 🎉结束时间：09-10</p></li><li><p><input checked="" disabled="" type="checkbox"> 🍀总结：SpringBoot 太好了，再也不需要麻烦的配置啦</p></li><li><p>学习目标</p></li></ul><table><thead><tr><th>章节</th><th>学习目标</th></tr></thead><tbody><tr><td><font color="#ff0000"><b>基础篇</b></font></td><td>能够创建SpringBoot工程<br/>基于SpringBoot实现ssm&#x2F;ssmp整合</td></tr></tbody></table><ul><li>前置知识</li></ul><table><thead><tr><th>章节</th><th>前置知识</th><th>要求</th></tr></thead><tbody><tr><td><font color="#ff0000"><b>基础篇</b></font></td><td>Java基础语法</td><td>面向对象，封装，继承，多态，类与接口，集合，IO，网络编程等</td></tr><tr><td><font color="#ff0000"><b>基础篇</b></font></td><td>Spring与SpringMVC</td><td>知道Spring是用来管理bean，能够基于Restful实现页面请求交互功能</td></tr><tr><td><font color="#ff0000"><b>基础篇</b></font></td><td>Mybatis与Mybatis-Plus</td><td>基于Mybatis和MybatisPlus能够开发出包含基础CRUD功能的标准Dao模块</td></tr><tr><td><font color="#ff0000"><b>基础篇</b></font></td><td>数据库MySQL</td><td>能够读懂基础CRUD功能的SQL语句</td></tr><tr><td><font color="#ff0000"><b>基础篇</b></font></td><td>服务器</td><td>知道服务器与web工程的关系，熟悉web服务器的基础配置</td></tr><tr><td><font color="#ff0000"><b>基础篇</b></font></td><td>maven</td><td>知道maven的依赖关系，知道什么是依赖范围，依赖传递，排除依赖，可选依赖，继承</td></tr><tr><td><font color="#ff0000"><b>基础篇</b></font></td><td>web技术（含vue，ElementUI)</td><td>知道vue如何发送ajax请求，如何获取响应数据，如何进行数据模型双向绑定</td></tr></tbody></table><h1 id="一、快速入门"><a href="#一、快速入门" class="headerlink" title="一、快速入门"></a>一、快速入门</h1><h2 id="1-手动创建项目"><a href="#1-手动创建项目" class="headerlink" title="1.手动创建项目"></a>1.手动创建项目</h2><h3 id="①创建Maven工程"><a href="#①创建Maven工程" class="headerlink" title="①创建Maven工程"></a>①创建Maven工程</h3><p><img src="https://img.jwt1399.top/img/202209061927726.png"></p><h3 id="②配置pom文件"><a href="#②配置pom文件" class="headerlink" title="②配置pom文件"></a>②配置pom文件</h3><blockquote><p>继承spring-boot-starter-parent；添加依赖spring-boot-starter-web</p></blockquote><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>parent</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework.boot<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-boot-starter-parent<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>2.7.0<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>parent</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependencies</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework.boot<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-boot-starter-web<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependencies</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="③创建引导类"><a href="#③创建引导类" class="headerlink" title="③创建引导类"></a>③创建引导类</h3><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@SpringBootApplication</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">MainApplication</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>        SpringApplication<span class="token punctuation">.</span><span class="token function">run</span><span class="token punctuation">(</span>MainApplication<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">,</span>args<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><code>@SpringBootApplication</code> 注解相当于使用 <code>@Configuration</code>、<code>@EnableAutoConfiguration</code> 和 <code>@ComponentScan</code> 及他们的默认属性</p><ul><li><p><code>@Configuration</code>作为配置类替代xml配置文件</p></li><li><p><code>@EnableAutoConfiguration</code>启用 SpringBoot 的自动配置机制</p></li><li><p><code>@ComponentScan</code> 自动扫描所有 Spring 组件</p></li></ul><h3 id="④编写业务"><a href="#④编写业务" class="headerlink" title="④编写业务"></a>④编写业务</h3><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@RestController</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">HelloController</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@GetMapping</span>    <span class="token keyword">public</span> String <span class="token function">home</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>        <span class="token keyword">return</span> <span class="token string">"Hello, Spring Boot 2!"</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="⑤运行项目"><a href="#⑤运行项目" class="headerlink" title="⑤运行项目"></a>⑤运行项目</h3><p>直接运行主程序 <code>MainApplication</code> 中的 <code>main</code> 方法，访问 <code>http://127.0.0.1:8080</code>，将会输出<code>Hello, Spring Boot 2!</code></p><h3 id="⑥简化部署"><a href="#⑥简化部署" class="headerlink" title="⑥简化部署"></a>⑥简化部署</h3><p>Spring Boot 包括了一个 Maven 插件，它可以将项目打包成一个可执行 jar</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>build</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>plugins</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>plugin</span><span class="token punctuation">></span></span>      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework.boot<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-boot-maven-plugin<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>2.7.0<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>plugin</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>plugins</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>build</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>点击 IDEA 右侧的 Maven ⇒ 点击生命周期 ⇒ 点击 clean ⇒ 点击 package，target 目录下就会生成 jar 包，执行如下命令即可运行 jar 包</p><pre class="line-numbers language-bash"><code class="language-bash">java -jar springBoot-learn-1.0-SNAPSHOT.jar<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><h2 id="2-脚手架创建项目"><a href="#2-脚手架创建项目" class="headerlink" title="2.脚手架创建项目"></a>2.脚手架创建项目</h2><p><a href="https://start.spring.io/">Spring Initailizr</a>是创建 Spring Boot 工程的脚手架，以后就创建 SpringBoot 项目就不用创建空白Maven项目，而是创建 Spring Initailizr 项目</p><p>Spirng程序相比，SpringBoot程序在开发的过程中各个层面均具有优势</p><table><thead><tr><th><strong>类配置文件</strong></th><th><strong>Spring</strong></th><th><strong>SpringBoot</strong></th></tr></thead><tbody><tr><td>pom文件中的坐标</td><td><strong>手工添加</strong></td><td><strong>勾选添加</strong></td></tr><tr><td>web3.0配置类</td><td><strong>手工制作</strong></td><td><strong>无</strong></td></tr><tr><td>Spring&#x2F;SpringMVC配置类</td><td><strong>手工制作</strong></td><td><strong>无</strong></td></tr><tr><td>控制器</td><td><strong>手工制作</strong></td><td><strong>手工制作</strong></td></tr></tbody></table><h3 id="①创建Spring-Initializr工程"><a href="#①创建Spring-Initializr工程" class="headerlink" title="①创建Spring Initializr工程"></a><strong>①创建Spring Initializr工程</strong></h3><blockquote><p>创建新模块，选择Spring Initializr，并配置模块相关基础信息</p></blockquote><p>创建工程时，可以切换 <code>starter</code> 服务路径为阿里云提供给的使用地址。</p><p>地址：<a href="http://start.aliyun.com或https//start.aliyun.com">http://start.aliyun.com或https://start.aliyun.com</a></p><p><img src="https://img.jwt1399.top/img/202209061917986.png"></p><h3 id="②选择技术集"><a href="#②选择技术集" class="headerlink" title="②选择技术集"></a>②选择技术集</h3><p><img src="https://img.jwt1399.top/img/202209061922725.png"></p><h3 id="③编写业务"><a href="#③编写业务" class="headerlink" title="③编写业务"></a><strong>③编写业务</strong></h3><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@RestController</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">HelloController</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@GetMapping</span>    <span class="token keyword">public</span> String <span class="token function">home</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>        <span class="token keyword">return</span> <span class="token string">"Hello, Spring Boot 2!"</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="④运行项目"><a href="#④运行项目" class="headerlink" title="④运行项目"></a>④运行项目</h3><p>直接运行主程序 <code>MainApplication</code> 中的 <code>main</code> 方法，访问 <code>http://127.0.0.1:8080</code>，将会输出<code>Hello, Spring Boot 2!</code></p><h1 id="二、工作流程"><a href="#二、工作流程" class="headerlink" title="二、工作流程"></a>二、工作流程</h1><p>SpringBoot带来的好处：</p><ul><li>起步依赖（简化依赖配置）<ul><li>依赖配置的书写简化就是靠这个起步依赖达成的</li></ul></li><li>自动配置（简化常用工程相关配置）<ul><li>配置过于繁琐，使用自动配置就可以做响应的简化，但是内部还是很复杂的</li></ul></li><li>辅助功能（内置服务器，……）<ul><li>比如我们没有配置Tomcat服务器，但是能正常运行。</li></ul></li></ul><p>下面结合入门程序来说说这些简化操作都在哪些方面进行体现的，一共分为4个方面</p><ul><li>parent</li><li>starter</li><li>引导类</li><li>内嵌tomcat</li></ul><h2 id="1-parent"><a href="#1-parent" class="headerlink" title="1.parent"></a>1.parent</h2><blockquote><p>SpringBoot 关注到开发者在进行开发时，往往对依赖版本的选择具有固定的搭配格式，并且这些依赖版本的选择还不能乱搭配。比如 A 技术的 2.0 版与 B 技术的 3.5 版可以合作在一起，但是和 B 技术的 3.7 版合并使用时就有冲突。于是 SpringBoot 做了无数个最合理的技术版本搭配列表，这个技术搭配列表的名字叫做<font color="#ff0000"><b>parent</b></font>。</p></blockquote><p>一句话总结：<font color="#ff0000"><b>使用parent可以帮助开发者进行版本的统一管理</b></font></p><p>那SpringBoot又是如何做到这一点的呢？可以查阅SpringBoot的配置源码，看到这些定义</p><ul><li>项目中的pom.xml中继承了一个坐标</li></ul><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>parent</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework.boot<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-boot-starter-parent<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>2.5.4<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>parent</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>打开后可以查阅到其中又继承了一个坐标</li></ul><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>parent</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework.boot<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-boot-dependencies<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>2.5.4<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>parent</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li><p>这个坐标中定义了两组信息，</p><ul><li><p>第一组是各式各样的<strong>依赖版本号</strong></p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>properties</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>activemq.version</span><span class="token punctuation">></span></span>5.16.3<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>activemq.version</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>aspectj.version</span><span class="token punctuation">></span></span>1.9.7<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>aspectj.version</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>assertj.version</span><span class="token punctuation">></span></span>3.19.0<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>assertj.version</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>javax-json.version</span><span class="token punctuation">></span></span>1.1.4<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>javax-json.version</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>javax-websocket.version</span><span class="token punctuation">></span></span>1.1<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>javax-websocket.version</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>jetty-el.version</span><span class="token punctuation">></span></span>9.0.48<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>jetty-el.version</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>junit.version</span><span class="token punctuation">></span></span>4.13.2<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>junit.version</span><span class="token punctuation">></span></span>    ......<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>properties</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li><li><p>第二组是各式各样的的<strong>依赖坐标信息</strong>。可以看出依赖坐标定义中没有具体的依赖版本号，而是引用了第一组信息中定义的依赖版本属性值</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependencyManagement</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependencies</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.hibernate<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>hibernate-core<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>${hibernate.version}<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>junit<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>junit<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>${junit.version}<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependencies</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependencyManagement</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></li></ul></li></ul><p><font color="#ff0000"><b>关注</b></font>：上面的依赖坐标定义是出现在&lt;dependencyManagement&gt;标签中的，其实是对引用坐标的依赖管理，并不是实际使用的坐标。因此当你的项目中继承了这组parent信息后，在不使用对应坐标的情况下，这组定义是不会具体导入某个依赖的</p><p><font color="#ff0000"><b>关注</b></font>：因为在maven中继承机会只有一次，上述继承的格式还可以切换成导入的形式进行，并且在阿里云的starter创建工程时就使用了此种形式</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependencyManagement</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependencies</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework.boot<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-boot-dependencies<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>${spring-boot.version}<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>type</span><span class="token punctuation">></span></span>pom<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>type</span><span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>scope</span><span class="token punctuation">></span></span>import<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>scope</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependencies</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependencyManagement</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>总结</strong></p><ol><li>开发SpringBoot程序要继承spring-boot-starter-parent</li><li>spring-boot-starter-parent中定义了若干个依赖管理</li><li>继承parent模块可以避免多个依赖使用相同技术时出现依赖版本冲突</li><li>继承parent的形式也可以采用引入依赖的形式实现效果</li></ol><h2 id="2-starter"><a href="#2-starter" class="headerlink" title="2.starter"></a>2.starter</h2><blockquote><p>SpringBoot 关注到开发者在实际开发时，对于依赖坐标的使用往往都有一些固定的组合方式，比如使用 spring-webmvc 就一定要使用 spring-web。每次都要固定搭配着写，非常繁琐。于是 SpringBoot 把所有的技术使用固定搭配格式都给开发出来，以后你用某个技术，就不用一次写一堆依赖了，直接用固定搭配格式就好了。这个固定技术搭配的名字叫做<font color="#ff0000"><b>starter</b></font>。</p></blockquote><p>一句话总结：<font color="#ff0000"><b>使用starter可以帮助开发者减少依赖配置</b></font></p><p>那SpringBoot又是如何做到这一点的呢？可以查阅SpringBoot的配置源码，看到这些定义</p><ul><li>项目中的pom.xml定义了使用SpringMVC技术，但是并没有写SpringMVC的坐标，而是添加了一个名字中包含starter的依赖</li></ul><p><code>command + shift + option + U</code>：以图的方式显示项目中依赖之间的关系。</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework.boot<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-boot-starter-web<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><ul><li>在spring-boot-starter-web中又定义了若干个具体依赖的坐标</li></ul><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependencies</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework.boot<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-boot-starter<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>2.5.4<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>scope</span><span class="token punctuation">></span></span>compile<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>scope</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework.boot<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-boot-starter-json<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>2.5.4<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>scope</span><span class="token punctuation">></span></span>compile<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>scope</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework.boot<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-boot-starter-tomcat<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>2.5.4<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>scope</span><span class="token punctuation">></span></span>compile<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>scope</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-web<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>5.3.9<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>scope</span><span class="token punctuation">></span></span>compile<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>scope</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-webmvc<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>5.3.9<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>scope</span><span class="token punctuation">></span></span>compile<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>scope</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependencies</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>发现里面有个 spring-boot-starter-json。看名称就知道，这个是与json有关的坐标了，但是看名字发现和最后两个又不太一样，它的名字中也有starter，打开看看里面有什么？</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependencies</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework.boot<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-boot-starter<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>2.5.4<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>scope</span><span class="token punctuation">></span></span>compile<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>scope</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-web<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>5.3.9<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>scope</span><span class="token punctuation">></span></span>compile<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>scope</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>com.fasterxml.jackson.core<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>jackson-databind<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>2.12.4<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>scope</span><span class="token punctuation">></span></span>compile<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>scope</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>com.fasterxml.jackson.datatype<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>jackson-datatype-jdk8<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>2.12.4<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>scope</span><span class="token punctuation">></span></span>compile<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>scope</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>com.fasterxml.jackson.datatype<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>jackson-datatype-jsr310<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>2.12.4<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>scope</span><span class="token punctuation">></span></span>compile<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>scope</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>com.fasterxml.jackson.module<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>jackson-module-parameter-names<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>2.12.4<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>scope</span><span class="token punctuation">></span></span>compile<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>scope</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependencies</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>我们可以发现，这个starter中又包含了若干个坐标，其实就是使用SpringMVC开发通常都会使用到Json，使用json又离不开这里面定义的这些坐标，看来还真是方便，SpringBoot把我们开发中使用的东西能用到的都给提前做好了。你仔细看完会发现，里面有一些你没用过的。的确会出现这种过量导入的可能性，没关系，可以通过maven中的排除依赖剔除掉一部分。不过你不管它也没事，大不了就是过量导入呗。</p><p>到这里基本上得到了一个信息，使用starter可以帮开发者快速配置依赖关系。以前写依赖3个坐标的，现在写导入一个就搞定了，就是加速依赖配置的。</p><p><strong>starter与parent的区别</strong></p><p>​朦朦胧胧中感觉starter与parent好像都是帮助我们简化配置的，但是功能又不一样，梳理一下。</p><p>​<font color="#ff0000"><b>starter</b></font>是一个坐标中定了若干个坐标，以前写多个的，现在写一个，<font color="#ff0000"><b>是用来减少依赖配置的书写量的</b></font></p><p>​<font color="#ff0000"><b>parent</b></font>是定义了几百个依赖版本号，以前写依赖需要自己手工控制版本，现在由SpringBoot统一管理，</p><p>这样就不存在版本冲突了，<font color="#ff0000"><b>是用来减少依赖冲突的</b></font></p><p><strong>实际开发应用方式</strong></p><ul><li><p>实际开发中如果需要用什么技术，先去找有没有这个技术对应的starter</p><ul><li>如果有对应的starter，直接写starter，而且无需指定版本，版本由parent提供</li><li>如果没有对应的starter，手写坐标即可</li></ul></li><li><p>实际开发中如果发现坐标出现了冲突现象，确认你要使用的可行的版本号，使用手工书写的方式添加对应依赖，覆盖SpringBoot提供给我们的配置管理</p><ul><li>方式一：直接写坐标</li><li>方式二：覆盖&lt;properties&gt;中定义的版本号，在当前项目里面重写配置，如下面的代码</li></ul><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>properties</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>mysql.version</span><span class="token punctuation">></span></span>5.1.43<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>mysql.version</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>properties</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre></li></ul><p><font color="#f0f"><b>温馨提示</b></font></p><p>​<a href="https://docs.spring.io/spring-boot/docs/current/reference/html/using.html#using.build-systems.starters">SpringBoot官方</a>给出了好多个starter的定义，方便我们使用，而且名称都是如下格式</p><pre class="line-numbers language-JAVA"><code class="language-JAVA">命名规则：spring-boot-starter-技术名称<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>那非官方定义的也有吗？有的，而且名称都是如下格式</p><pre class="line-numbers language-java"><code class="language-java">命名规则：技术名称<span class="token operator">-</span>spring<span class="token operator">-</span>boot<span class="token operator">-</span>starter    或者<span class="token operator">:</span> 技术名称<span class="token operator">-</span>boot<span class="token operator">-</span>starter（技术名称过长，简化命名）<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p><strong>总结</strong></p><ol><li>开发SpringBoot程序需要导入坐标时通常导入对应的starter</li><li>每个不同的starter根据功能不同，通常包含多个依赖坐标</li><li>使用starter可以实现快速配置的效果，达到简化配置的目的</li></ol><h2 id="3-引导类"><a href="#3-引导类" class="headerlink" title="3.引导类"></a>3.引导类</h2><p>程序是如何运行的。目前程序运行的入口就是SpringBoot工程创建时自带的那个类了，带有main方法的那个类，运行这个类就可以启动SpringBoot工程的运行</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@SpringBootApplication</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">SpringbootApplication</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>        SpringApplication<span class="token punctuation">.</span><span class="token function">run</span><span class="token punctuation">(</span>SpringbootApplication<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">,</span> args<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>SpringBoot本身是为了加速Spring程序的开发的，而Spring程序运行的基础是需要创建自己的Spring容器对象（IoC容器）并将所有的对象交给Spring的容器管理，也就是一个一个的Bean。当这个类运行后就会产生一个Spring容器对象，并且可以将这个对象保存起来，通过容器对象直接操作Bean。</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@SpringBootApplication</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">SpringbootApplication</span> <span class="token punctuation">{</span>  <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>      <span class="token comment" spellcheck="true">//1、返回我们IOC容器</span>      ConfigurableApplicationContext run <span class="token operator">=</span> SpringApplication<span class="token punctuation">.</span><span class="token function">run</span><span class="token punctuation">(</span>MainApplication<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">,</span> args<span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token comment" spellcheck="true">//2、查看容器里面的组件</span>      String<span class="token punctuation">[</span><span class="token punctuation">]</span> names <span class="token operator">=</span> run<span class="token punctuation">.</span><span class="token function">getBeanDefinitionNames</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token keyword">for</span> <span class="token punctuation">(</span>String name <span class="token operator">:</span> names<span class="token punctuation">)</span> <span class="token punctuation">{</span>          System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>name<span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token punctuation">}</span>  <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>作为一个引导类最典型的特征就是当前类上方声明了一个注解<font color="#ff0000"><b>@SpringBootApplication</b></font></p><p><strong>总结</strong></p><ol><li>SpringBoot工程提供引导类用来启动程序</li><li>SpringBoot工程启动后创建并初始化Spring容器</li></ol><h2 id="4-内嵌tomcat"><a href="#4-内嵌tomcat" class="headerlink" title="4.内嵌tomcat"></a>4.内嵌tomcat</h2><p>当前我们做的SpringBoot入门案例勾选了Spirng-web的功能，并且导入了对应的starter。</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework.boot<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-boot-starter-web<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>​SpringBoot发现，既然你要做web程序，肯定离不开使用web服务器，我再帮你搞一个web服务器，你要愿意用的，直接使用就好了，干脆我再多给你几种选择，你随便切换。万一你不想用我给你提供的，也行，你可以自己搞。</p><p>​由于这个功能不属于程序的主体功能，可用可不用，于是乎SpringBoot将其定位成辅助功能。</p><p>​下面就围绕着这个内置的web服务器，也可以说是内置的tomcat服务器来研究几个问题</p><ol><li>这个服务器在什么位置定义的？</li><li>这个服务器是怎么运行的？</li><li>这个服务器如果想换怎么换？</li></ol><p><strong>内嵌Tomcat定义位置</strong></p><p>​说到定义的位置，我们就想，如果我们不开发web程序，用的着web服务器吗？肯定用不着啊。那如果这个东西被加入到你的程序中，伴随着什么技术进来的呢？肯定是web相关的功能啊，没错，就是前面导入的web相关的starter做的这件事。</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework.boot<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-boot-starter-web<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>​打开查看web的starter导入了哪些东西</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependencies</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework.boot<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-boot-starter<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>2.5.4<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>scope</span><span class="token punctuation">></span></span>compile<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>scope</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework.boot<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-boot-starter-json<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>2.5.4<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>scope</span><span class="token punctuation">></span></span>compile<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>scope</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework.boot<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-boot-starter-tomcat<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>2.5.4<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>scope</span><span class="token punctuation">></span></span>compile<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>scope</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-web<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>5.3.9<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>scope</span><span class="token punctuation">></span></span>compile<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>scope</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-webmvc<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>5.3.9<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>scope</span><span class="token punctuation">></span></span>compile<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>scope</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependencies</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>​第三个依赖就是这个tomcat对应的东西了，居然也是一个starter，再打开看看</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependencies</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>jakarta.annotation<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>jakarta.annotation-api<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>1.3.5<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>scope</span><span class="token punctuation">></span></span>compile<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>scope</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.apache.tomcat.embed<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>tomcat-embed-core<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>9.0.52<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>scope</span><span class="token punctuation">></span></span>compile<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>scope</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>exclusions</span><span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>exclusion</span><span class="token punctuation">></span></span>                <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>tomcat-annotations-api<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>                <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.apache.tomcat<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>exclusion</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>exclusions</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.apache.tomcat.embed<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>tomcat-embed-el<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>9.0.52<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>scope</span><span class="token punctuation">></span></span>compile<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>scope</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.apache.tomcat.embed<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>tomcat-embed-websocket<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>9.0.52<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>scope</span><span class="token punctuation">></span></span>compile<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>scope</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>exclusions</span><span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>exclusion</span><span class="token punctuation">></span></span>                <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>tomcat-annotations-api<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>                <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.apache.tomcat<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>exclusion</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>exclusions</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependencies</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>这里面有一个核心的坐标，tomcat-embed-core，叫做tomcat内嵌核心。就是这个东西把tomcat功能引入到了我们的程序中。目前解决了第一个问题。谁把tomcat引入到程序中的？spring-boot-starter-web中的spring-boot-starter-tomcat做的。</p><p><strong>内嵌Tomcat运行原理</strong></p><p>Tomcat服务器是一款软件，而且是一款使用java语言开发的软件下面的问题来了，既然是使用java语言开发的，运行的时候肯定符合java程序运行的原理，java程序运行靠的是什么？对象呀，一切皆对象，万物皆对象。那tomcat运行起来呢？也是对象。</p><p>​如果是对象，那Spring容器是用来管理对象的，tomcat服务器运行其实是以对象的形式在Spring容器中运行的。具体运行的是什么呢？其实就是上前面提到的那个tomcat内嵌核心</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependencies</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.apache.tomcat.embed<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>tomcat-embed-core<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>9.0.52<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>scope</span><span class="token punctuation">></span></span>compile<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>scope</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependencies</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>那既然是个对象，如果把这个对象从Spring容器中去掉是不是就没有web服务器的功能呢？是这样的，通过依赖排除可以去掉这个web服务器功能</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependencies</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework.boot<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-boot-starter-web<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>exclusions</span><span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>exclusion</span><span class="token punctuation">></span></span>                <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework.boot<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>                <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-boot-starter-tomcat<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>exclusion</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>exclusions</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependencies</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>重新启动程序可以观察到程序运行了，但是并没有像之前那样运行后会等着用户发请求，而是直接停掉了。</p><p><strong>更换内嵌Tomcat</strong></p><p>根据SpringBoot的工作机制，用什么技术，加入什么依赖就行了。SpringBoot提供了3款内置的服务器</p><ul><li><p>tomcat(默认)：apache出品，粉丝多，应用面广，负载了若干较重的组件</p></li><li><p>jetty：更轻量级，负载性能远不及tomcat</p></li><li><p>undertow：负载性能勉强跑赢tomcat</p></li></ul><p>想用哪个，加个坐标就OK。前提是把tomcat排除掉，因为tomcat是默认加载的。</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependencies</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework.boot<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-boot-starter-web<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>exclusions</span><span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>exclusion</span><span class="token punctuation">></span></span>                <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework.boot<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>                <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-boot-starter-tomcat<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>exclusion</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>exclusions</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework.boot<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-boot-starter-jetty<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependencies</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>现在就已经成功替换了web服务器，核心思想就是用什么加入对应坐标就可以了。如果有starter，优先使用starter。</p><p><strong>总结</strong></p><ol><li>内嵌Tomcat服务器是SpringBoot辅助功能之一</li><li>内嵌Tomcat工作原理是将Tomcat服务器作为对象运行，并将该对象交给Spring容器管理</li><li>变更内嵌服务器思想是去除现有服务器，添加全新的服务器</li></ol><h1 id="三、基础配置"><a href="#三、基础配置" class="headerlink" title="三、基础配置"></a>三、基础配置</h1><h2 id="1-属性配置"><a href="#1-属性配置" class="headerlink" title="1.属性配置"></a>1.属性配置</h2><p>SpringBoot 通过 resources 目录下的配置文件 <code>application.properties</code> 就可以修改默认的配置</p><p>例如 将 tomcat 的默认端口8080改成80，输入port后，自带提示带提示</p><pre class="line-numbers language-properties"><code class="language-properties"><span class="token attr-name">server.port</span><span class="token punctuation">=</span><span class="token attr-value">80</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>重新运行，浏览器输入<code>http://localhost</code>即可</p><p>这个配置项和什么有关？在pom中注释掉导入的spring-boot-starter-web，然后刷新工程，你会发现配置的提示消失了。闹了半天是设定使用了什么技术才能做什么配置。</p><p>更多配置项请参考：<a href="https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html#appendix.application-properties">Common Application Properties</a></p><p><strong>总结</strong></p><ol><li>SpringBoot中导入对应starter后，提供对应配置属性</li><li>书写SpringBoot配置采用关键字+提示形式书写</li></ol><h2 id="2-配置文件分类"><a href="#2-配置文件分类" class="headerlink" title="2.配置文件分类"></a>2.配置文件分类</h2><p>SpringBoot除了支持properties格式的配置文件，还支持另外两种格式的配置文件。分别如下:</p><ul><li>properties格式</li></ul><pre class="line-numbers language-properties"><code class="language-properties"><span class="token attr-name">server.port</span><span class="token punctuation">=</span><span class="token attr-value">80</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><ul><li>yml格式（<code>推荐使用</code>）</li></ul><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">server</span><span class="token punctuation">:</span>  <span class="token key atrule">port</span><span class="token punctuation">:</span> <span class="token number">81</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><ul><li>yaml格式</li></ul><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">server</span><span class="token punctuation">:</span>  <span class="token key atrule">port</span><span class="token punctuation">:</span> <span class="token number">82</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>yml 格式和 yaml 格式除了文件名后缀不一样，格式完全一样，所以可以合并成一种格式来看。</p><ul><li>三种格式中推荐使用 yml 格式</li><li>配置文件间的加载优先级properties（最高）&gt;  yml  &gt;  yaml（最低）</li><li>不同配置文件中相同配置按照加载优先级相互覆盖，不同配置文件中不同配置全部保留</li></ul><h2 id="3-yaml文件格式"><a href="#3-yaml文件格式" class="headerlink" title="3.yaml文件格式"></a>3.yaml文件格式</h2><p>YAML（YAML Ain’t Markup Language） 是一种较为人性化的<strong>数据序列化语言</strong> ，语法比较简洁直观，特点是使用空格来表达层次结构，其最大优势在于<strong>数据结构</strong>方面的表达，所以 YAML 更多应用于<strong>编写配置文件</strong>，其文件一般以 <strong>.yml</strong> 为后缀。</p><p>yaml 非常适合用来做以数据为中心的配置文件。所以我们可以用 yaml 代替之前的 properties</p><h3 id="①基本语法"><a href="#①基本语法" class="headerlink" title="①基本语法"></a><strong>①基本语法</strong></h3><ul><li>key: value    <ul><li>“<code>:</code>”后有空格</li></ul></li><li>大小写敏感</li><li>使用缩进表示层级关系</li><li>缩进不允许使用 tab，只允许空格</li><li>缩进的空格数不重要，只要相同层级的元素左对齐即可</li><li>‘#’表示注释</li><li>字符串无需加引号，如果要加：<ul><li>双引号表示字符串内容会被转义</li><li>单引号表示字符串内容不会被转义</li></ul></li></ul><p><strong>数据类型</strong></p><ul><li>普通数据：单个的、不可再分的值。date、boolean、string、number、null</li></ul><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">k</span><span class="token punctuation">:</span> v<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><ul><li>对象：键值对的集合。map、hash、set、object</li></ul><pre class="line-numbers language-yaml"><code class="language-yaml">行内写法：  k<span class="token punctuation">:</span> <span class="token punctuation">{</span>k1<span class="token punctuation">:</span>v1<span class="token punctuation">,</span>k2<span class="token punctuation">:</span>v2<span class="token punctuation">,</span>k3<span class="token punctuation">:</span>v3<span class="token punctuation">}</span><span class="token comment" spellcheck="true">#或</span><span class="token key atrule">k</span><span class="token punctuation">:</span>     <span class="token key atrule">k1</span><span class="token punctuation">:</span> v1  <span class="token key atrule">k2</span><span class="token punctuation">:</span> v2  <span class="token key atrule">k3</span><span class="token punctuation">:</span> v3<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>数组：一组按次序排列的值。array、list、queue</li></ul><pre class="line-numbers language-yaml"><code class="language-yaml">行内写法：  k<span class="token punctuation">:</span> <span class="token punctuation">[</span>v1<span class="token punctuation">,</span>v2<span class="token punctuation">,</span>v3<span class="token punctuation">]</span><span class="token comment" spellcheck="true">#或</span><span class="token key atrule">k</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> v1 <span class="token punctuation">-</span> v2 <span class="token punctuation">-</span> v3 <span class="token key atrule">users</span><span class="token punctuation">:</span> <span class="token comment" spellcheck="true">#对象数组格式</span>  <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Tom       <span class="token key atrule">age</span><span class="token punctuation">:</span> <span class="token number">4</span>  <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Jerry    <span class="token key atrule">age</span><span class="token punctuation">:</span> <span class="token number">5    </span><span class="token key atrule">users2</span><span class="token punctuation">:</span> <span class="token punctuation">[</span> <span class="token punctuation">{</span> name<span class="token punctuation">:</span>Tom <span class="token punctuation">,</span> age<span class="token punctuation">:</span><span class="token number">4 </span><span class="token punctuation">}</span> <span class="token punctuation">,</span> <span class="token punctuation">{</span> name<span class="token punctuation">:</span>Jerry <span class="token punctuation">,</span> age<span class="token punctuation">:</span><span class="token number">5 </span><span class="token punctuation">}</span> <span class="token punctuation">]</span><span class="token comment" spellcheck="true">#对象数组缩略格式</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>示例</strong></p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Data</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Person</span> <span class="token punctuation">{</span>        <span class="token keyword">private</span> String userName<span class="token punctuation">;</span>    <span class="token keyword">private</span> Boolean boss<span class="token punctuation">;</span>    <span class="token keyword">private</span> Date birth<span class="token punctuation">;</span>    <span class="token keyword">private</span> Integer age<span class="token punctuation">;</span>    <span class="token keyword">private</span> Pet pet<span class="token punctuation">;</span>    <span class="token keyword">private</span> String<span class="token punctuation">[</span><span class="token punctuation">]</span> interests<span class="token punctuation">;</span>    <span class="token keyword">private</span> List<span class="token operator">&lt;</span>String<span class="token operator">></span> animal<span class="token punctuation">;</span>    <span class="token keyword">private</span> Map<span class="token operator">&lt;</span>String<span class="token punctuation">,</span> Object<span class="token operator">></span> score<span class="token punctuation">;</span>    <span class="token keyword">private</span> Set<span class="token operator">&lt;</span>Double<span class="token operator">></span> salarys<span class="token punctuation">;</span>    <span class="token keyword">private</span> Map<span class="token operator">&lt;</span>String<span class="token punctuation">,</span> List<span class="token operator">&lt;</span>Pet<span class="token operator">>></span> allPets<span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token annotation punctuation">@Data</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Pet</span> <span class="token punctuation">{</span>    <span class="token keyword">private</span> String name<span class="token punctuation">;</span>    <span class="token keyword">private</span> Double weight<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token comment" spellcheck="true"># yaml表示以上对象</span><span class="token key atrule">person</span><span class="token punctuation">:</span>  <span class="token key atrule">userName</span><span class="token punctuation">:</span> zhangsan  <span class="token key atrule">boss</span><span class="token punctuation">:</span> <span class="token boolean important">false</span>  <span class="token key atrule">birth</span><span class="token punctuation">:</span> 2019/12/12 20<span class="token punctuation">:</span><span class="token datetime number">12:33</span>  <span class="token key atrule">age</span><span class="token punctuation">:</span> <span class="token number">18</span>  <span class="token key atrule">pet</span><span class="token punctuation">:</span>     <span class="token key atrule">name</span><span class="token punctuation">:</span> tomcat    <span class="token key atrule">weight</span><span class="token punctuation">:</span> <span class="token number">23.4</span>  <span class="token key atrule">interests</span><span class="token punctuation">:</span> <span class="token punctuation">[</span>篮球<span class="token punctuation">,</span>游泳<span class="token punctuation">]</span>  <span class="token key atrule">animal</span><span class="token punctuation">:</span>     <span class="token punctuation">-</span> jerry    <span class="token punctuation">-</span> mario  <span class="token key atrule">score</span><span class="token punctuation">:</span>    <span class="token key atrule">english</span><span class="token punctuation">:</span>       <span class="token key atrule">first</span><span class="token punctuation">:</span> <span class="token number">30</span>      <span class="token key atrule">second</span><span class="token punctuation">:</span> <span class="token number">40</span>      <span class="token key atrule">third</span><span class="token punctuation">:</span> <span class="token number">50</span>    <span class="token key atrule">math</span><span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token number">131</span><span class="token punctuation">,</span><span class="token number">140</span><span class="token punctuation">,</span><span class="token number">148</span><span class="token punctuation">]</span>    <span class="token key atrule">chinese</span><span class="token punctuation">:</span> <span class="token punctuation">{</span><span class="token key atrule">first</span><span class="token punctuation">:</span> <span class="token number">128</span><span class="token punctuation">,</span><span class="token key atrule">second</span><span class="token punctuation">:</span> <span class="token number">136</span><span class="token punctuation">}</span>  <span class="token key atrule">salarys</span><span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token number">3999</span><span class="token punctuation">,</span><span class="token number">4999.98</span><span class="token punctuation">,</span><span class="token number">5999.99</span><span class="token punctuation">]</span>  <span class="token key atrule">allPets</span><span class="token punctuation">:</span>    <span class="token key atrule">sick</span><span class="token punctuation">:</span>      <span class="token punctuation">-</span> <span class="token punctuation">{</span><span class="token key atrule">name</span><span class="token punctuation">:</span> tom<span class="token punctuation">}</span>      <span class="token punctuation">-</span> <span class="token punctuation">{</span><span class="token key atrule">name</span><span class="token punctuation">:</span> jerry<span class="token punctuation">,</span><span class="token key atrule">weight</span><span class="token punctuation">:</span> <span class="token number">47</span><span class="token punctuation">}</span>    <span class="token key atrule">health</span><span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token punctuation">{</span><span class="token key atrule">name</span><span class="token punctuation">:</span> mario<span class="token punctuation">,</span><span class="token key atrule">weight</span><span class="token punctuation">:</span> <span class="token number">47</span><span class="token punctuation">}</span><span class="token punctuation">]</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="②数据引用"><a href="#②数据引用" class="headerlink" title="②数据引用"></a>②数据引用</h3><p>如果你在书写yaml数据时，经常出现如下现象，比如很多个文件都具有相同的目录前缀</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">center</span><span class="token punctuation">:</span>    <span class="token key atrule">dataDir</span><span class="token punctuation">:</span> /usr/local/fire/data    <span class="token key atrule">tmpDir</span><span class="token punctuation">:</span> /usr/local/fire/tmp    <span class="token key atrule">logDir</span><span class="token punctuation">:</span> /usr/local/fire/log    <span class="token key atrule">msgDir</span><span class="token punctuation">:</span> /usr/local/fire/msgDir<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>或者</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">center</span><span class="token punctuation">:</span>    <span class="token key atrule">dataDir</span><span class="token punctuation">:</span> D<span class="token punctuation">:</span>/usr/local/fire/data    <span class="token key atrule">tmpDir</span><span class="token punctuation">:</span> D<span class="token punctuation">:</span>/usr/local/fire/tmp    <span class="token key atrule">logDir</span><span class="token punctuation">:</span> D<span class="token punctuation">:</span>/usr/local/fire/log    <span class="token key atrule">msgDir</span><span class="token punctuation">:</span> D<span class="token punctuation">:</span>/usr/local/fire/msgDir<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>​这个时候你可以使用引用格式来定义数据，其实就是搞了个变量名，然后引用变量了，格式如下：</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">baseDir</span><span class="token punctuation">:</span> /usr/local/fire    <span class="token key atrule">center</span><span class="token punctuation">:</span>    <span class="token key atrule">dataDir</span><span class="token punctuation">:</span> $<span class="token punctuation">{</span>baseDir<span class="token punctuation">}</span>/data    <span class="token key atrule">tmpDir</span><span class="token punctuation">:</span> $<span class="token punctuation">{</span>baseDir<span class="token punctuation">}</span>/tmp    <span class="token key atrule">logDir</span><span class="token punctuation">:</span> $<span class="token punctuation">{</span>baseDir<span class="token punctuation">}</span>/log    <span class="token key atrule">msgDir</span><span class="token punctuation">:</span> $<span class="token punctuation">{</span>baseDir<span class="token punctuation">}</span>/msgDir<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>还有一个注意事项，在书写字符串时，如果需要使用转义字符，需要将数据字符串使用双引号包裹起来</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">lesson</span><span class="token punctuation">:</span> <span class="token string">"Spring\tboot\nlesson"</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p><strong>总结</strong></p><ol><li>在配置文件中可以使用${属性名}方式引用属性值</li><li>如果属性中出现特殊字符，可以使用双引号包裹起来作为字符解析</li></ol><h2 id="4-yaml数据读取"><a href="#4-yaml数据读取" class="headerlink" title="4.yaml数据读取"></a>4.yaml数据读取</h2><h3 id="①读取单一数据"><a href="#①读取单一数据" class="headerlink" title="①读取单一数据"></a>①读取单一数据</h3><p>​yaml中保存的单个数据，可以使用Spring中的注解直接读取，使用<code>@Value</code>可以读取单个数据，属性名引用方式：<font color="#ff0000"><b>${一级属性名.二级属性名……}</b></font></p><img src="https://img.jwt1399.top/img/202209062142453.png" style="zoom:80%;" /><p><strong>总结</strong></p><ol><li>使用@Value读取单个数据</li><li>如果数据存在多层级，依次书写层级名称即可</li></ol><h3 id="②读取全部数据"><a href="#②读取全部数据" class="headerlink" title="②读取全部数据"></a>②读取全部数据</h3><p>​读取单一数据可以解决读取数据的问题，但是如果定义的数据量过大，这么一个一个书写肯定会累死人的，SpringBoot提供了一个对象，能够把所有的数据都封装到这一个对象中，这个对象叫做Environment，使用自动装配注解 @Autowired 可以将所有的yaml数据封装到这个对象中</p><img src="https://img.jwt1399.top/img/202209062148870.png" style="zoom:80%;" /><p>数据封装到了Environment对象中，通过方法 <strong>getProperty(“属性名”)</strong> 获取数据</p><p><strong>总结</strong></p><ol><li>使用Environment对象封装全部配置信息</li><li>使用@Autowired自动装配数据到Environment对象中</li></ol><h3 id="③读取对象数据"><a href="#③读取对象数据" class="headerlink" title="③读取对象数据"></a>③读取对象数据</h3><p>单一数据读取书写比较繁琐，全数据封装又封装的太厉害了，每次拿数据还要一个一个的 getProperty() ，总之用起来都不是很舒服。由于Java是一个面向对象的语言，很多情况下，我们会将一组数据封装成一个对象。SpringBoot也提供了可以将一组yaml对象数据封装一个Java对象的操作</p><p>​首先定义一个对象，并将该对象纳入Spring管控的范围，也就是定义成一个bean，然后使用注解<code>@ConfigurationProperties(prefix = &quot;数据前缀&quot;)</code>  指定该对象加载哪一组yaml中配置的信息。</p><img src="https://img.jwt1399.top/img/202209062150640.png"  style="zoom:80%;" /><p>@ConfigurationProperties 必须指定加载的数据前缀是什么，这样当前前缀下的所有属性就封装到这个对象中。记得数据属性名要与对象的变量名一一对应，不然没法封装。其实以后如果你要定义一组数据自己使用，就可以先写一个对象，然后定义好属性，下面到配置中根据这个格式书写即可。</p><img src="https://img.jwt1399.top/img/202209062151985.png" style="zoom:80%;" /><p><strong>总结</strong></p><ol><li>使用@ConfigurationProperties注解绑定配置信息到封装类中</li><li>封装类需要定义为Spring管理的bean，否则无法进行属性注入</li></ol><p>​<font color="#f0f"><b>温馨提示</b></font></p><p>​细心的小伙伴会发现一个问题，自定义的这种数据在yaml文件中书写时没有弹出提示，解决方法如下。</p><h3 id="④配置提示效果"><a href="#④配置提示效果" class="headerlink" title="④配置提示效果"></a>④配置提示效果</h3><blockquote><p>配置提示 configuration-processor</p></blockquote><p>自定义的类和配置文件绑定一般没有提示。若要提示，添加如下依赖：</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework.boot<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-boot-configuration-processor<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>optional</span><span class="token punctuation">></span></span>true<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>optional</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>build</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>plugins</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>plugin</span><span class="token punctuation">></span></span>      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>configuration</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>excludes</span><span class="token punctuation">></span></span>          <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>exclude</span><span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework.boot<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-boot-configuration-processor<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>          <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>exclude</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>excludes</span><span class="token punctuation">></span></span>      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>configuration</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>plugin</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>plugins</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>build</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>再在自定义的类上添加配置注解</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@ConfigurationProperties</span><span class="token punctuation">(</span>prefix <span class="token operator">=</span> <span class="token string">"xxx"</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true">//读取文件中所有以xxx开头的属性，并和bean中的字段进行匹配</span><span class="token annotation punctuation">@Component</span> <span class="token comment" spellcheck="true">//标注类为Spring容器的Bean</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>测试</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@ConfigurationProperties</span><span class="token punctuation">(</span>prefix <span class="token operator">=</span> <span class="token string">"user"</span><span class="token punctuation">)</span><span class="token annotation punctuation">@Component</span> <span class="token comment" spellcheck="true">//标注类为Spring容器的Bean</span><span class="token annotation punctuation">@NoArgsConstructor</span> <span class="token comment" spellcheck="true">//无参构造</span><span class="token annotation punctuation">@AllArgsConstructor</span> <span class="token comment" spellcheck="true">//有参构造</span><span class="token annotation punctuation">@Data</span><span class="token annotation punctuation">@EqualsAndHashCode</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">User</span> <span class="token punctuation">{</span>   <span class="token keyword">private</span> String name<span class="token punctuation">;</span>   <span class="token keyword">private</span> Integer age<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>在配置文件 application.yml 中输入 user 就会得到 name 和 age 的输入提示</p><h1 id="四、整合技术"><a href="#四、整合技术" class="headerlink" title="四、整合技术"></a>四、整合技术</h1><p>整合第三方技术通用方式</p><ul><li>导入对应的starter</li><li>配置非默认值对应的配置项</li></ul><h2 id="1-整合JUnit"><a href="#1-整合JUnit" class="headerlink" title="1.整合JUnit"></a>1.整合JUnit</h2><ul><li>导入测试对应的starter，初始化项目时此项是默认导入的</li></ul><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework.boot<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-boot-starter-test<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>scope</span><span class="token punctuation">></span></span>test<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>scope</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>在测试类上添加注解 @SpringBootTest ，在方法上添加注解 @Test</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@SpringBootTest</span><span class="token keyword">class</span> <span class="token class-name">SpringbootJunitTests</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">//注入你要测试的对象</span>    <span class="token annotation punctuation">@Autowired</span>    <span class="token keyword">private</span> BookDao bookDao<span class="token punctuation">;</span>    <span class="token annotation punctuation">@Test</span>    <span class="token keyword">void</span> <span class="token function">contextLoads</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">//执行要测试的对象对应的方法</span>        bookDao<span class="token punctuation">.</span><span class="token function">save</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"two..."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>测试类如果存在于引导类所在包或子包中无需指定引导类，测试类如果不存在于引导类所在的包或子包中需要通过classes属性指定引导类</li></ul><p>第一种方式使用属性的形式进行，在注解 @SpringBootTest 中添加 classes 属性指定配置类</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@SpringBootTest</span><span class="token punctuation">(</span>classes <span class="token operator">=</span> SpringbootJunitApplication<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token keyword">class</span> <span class="token class-name">SpringbootJunitApplicationTests</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">//注入你要测试的对象</span>    <span class="token annotation punctuation">@Autowired</span>    <span class="token keyword">private</span> BookDao bookDao<span class="token punctuation">;</span>    <span class="token annotation punctuation">@Test</span>    <span class="token keyword">void</span> <span class="token function">contextLoads</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">//执行要测试的对象对应的方法</span>        bookDao<span class="token punctuation">.</span><span class="token function">save</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"two..."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>第二种方式回归原始配置方式，仍然使用 @ContextConfiguration 注解进行，效果是一样的</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@SpringBootTest</span><span class="token annotation punctuation">@ContextConfiguration</span><span class="token punctuation">(</span>classes <span class="token operator">=</span> SpringbootJunitApplication<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token keyword">class</span> <span class="token class-name">SpringbootJunitApplicationTests</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">//注入你要测试的对象</span>    <span class="token annotation punctuation">@Autowired</span>    <span class="token keyword">private</span> BookDao bookDao<span class="token punctuation">;</span>    <span class="token annotation punctuation">@Test</span>    <span class="token keyword">void</span> <span class="token function">contextLoads</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">//执行要测试的对象对应的方法</span>        bookDao<span class="token punctuation">.</span><span class="token function">save</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"two..."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>总结</strong></p><ol><li>导入测试对应的starter</li><li>测试类使用@SpringBootTest修饰</li><li>使用自动装配的形式添加要测试的对象</li><li>测试类如果存在于引导类所在包或子包中无需指定引导类</li><li>测试类如果不存在于引导类所在的包或子包中需要通过classes属性指定引导类</li></ol><h2 id="2-整合MyBatis"><a href="#2-整合MyBatis" class="headerlink" title="2.整合MyBatis"></a>2.整合MyBatis</h2><p><strong>步骤①</strong>：创建模块时勾选要使用的技术，MyBatis，由于要操作数据库，还要勾选对应数据库</p><p><img src="https://img.jwt1399.top/img/202209071920462.png"></p><p>或者手工导入 MyBatis 的 starter 和对应数据库的坐标</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.mybatis.spring.boot<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>mybatis-spring-boot-starter<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>2.2.2<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>mysql<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>mysql-connector-java<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>scope</span><span class="token punctuation">></span></span>runtime<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>scope</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤②</strong>：配置数据源相关信息，没有这个信息你连接哪个数据库都不知道</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">spring</span><span class="token punctuation">:</span>  <span class="token key atrule">datasource</span><span class="token punctuation">:</span>    <span class="token key atrule">driver-class-name</span><span class="token punctuation">:</span> com.mysql.cj.jdbc.Driver    <span class="token key atrule">url</span><span class="token punctuation">:</span> jdbc<span class="token punctuation">:</span>mysql<span class="token punctuation">:</span>//localhost<span class="token punctuation">:</span>3306/ssm_db<span class="token punctuation">?</span>serverTimezone=Asia/Shanghai    <span class="token key atrule">username</span><span class="token punctuation">:</span> root    <span class="token key atrule">password</span><span class="token punctuation">:</span> root<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>测试</strong></p><ul><li>测试表</li></ul><pre class="line-numbers language-sql"><code class="language-sql"><span class="token keyword">SET</span> NAMES utf8<span class="token punctuation">;</span><span class="token keyword">SET</span> FOREIGN_KEY_CHECKS <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">-- ----------------------------</span><span class="token comment" spellcheck="true">-- Table structure for tbl_book</span><span class="token comment" spellcheck="true">-- ----------------------------</span><span class="token keyword">DROP</span> <span class="token keyword">TABLE</span> <span class="token keyword">IF</span> <span class="token keyword">EXISTS</span> <span class="token punctuation">`</span>tbl_book<span class="token punctuation">`</span><span class="token punctuation">;</span><span class="token keyword">CREATE</span> <span class="token keyword">TABLE</span> <span class="token punctuation">`</span>tbl_book<span class="token punctuation">`</span>  <span class="token punctuation">(</span>  <span class="token punctuation">`</span>id<span class="token punctuation">`</span> <span class="token keyword">int</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">AUTO_INCREMENT</span><span class="token punctuation">,</span>  <span class="token punctuation">`</span>name<span class="token punctuation">`</span> <span class="token keyword">varchar</span><span class="token punctuation">(</span><span class="token number">50</span><span class="token punctuation">)</span> <span class="token keyword">CHARACTER SET</span> utf8 <span class="token keyword">COLLATE</span> utf8_general_ci <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span>  <span class="token punctuation">`</span><span class="token keyword">type</span><span class="token punctuation">`</span> <span class="token keyword">varchar</span><span class="token punctuation">(</span><span class="token number">100</span><span class="token punctuation">)</span> <span class="token keyword">CHARACTER SET</span> utf8 <span class="token keyword">COLLATE</span> utf8_general_ci <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span>  <span class="token punctuation">`</span>description<span class="token punctuation">`</span> <span class="token keyword">varchar</span><span class="token punctuation">(</span><span class="token number">100</span><span class="token punctuation">)</span> <span class="token keyword">CHARACTER SET</span> utf8 <span class="token keyword">COLLATE</span> utf8_general_ci <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span>  <span class="token keyword">PRIMARY</span> <span class="token keyword">KEY</span> <span class="token punctuation">(</span><span class="token punctuation">`</span>id<span class="token punctuation">`</span><span class="token punctuation">)</span> <span class="token keyword">USING</span> <span class="token keyword">BTREE</span><span class="token punctuation">)</span> <span class="token keyword">ENGINE</span> <span class="token operator">=</span> <span class="token keyword">InnoDB</span> <span class="token keyword">CHARACTER SET</span> <span class="token operator">=</span> utf8 <span class="token keyword">COLLATE</span> <span class="token operator">=</span> utf8_general_ci ROW_FORMAT <span class="token operator">=</span> Dynamic<span class="token punctuation">;</span><span class="token comment" spellcheck="true">-- ----------------------------</span><span class="token comment" spellcheck="true">-- Records of tbl_book</span><span class="token comment" spellcheck="true">-- ----------------------------</span><span class="token keyword">INSERT</span> <span class="token keyword">INTO</span> <span class="token punctuation">`</span>tbl_book<span class="token punctuation">`</span> <span class="token keyword">VALUES</span> <span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token string">'三体'</span><span class="token punctuation">,</span> <span class="token string">'科幻'</span><span class="token punctuation">,</span> <span class="token string">'大刘的巅峰之作，将中国科幻推向世界舞台。总共分为三部曲：《地球往事》、《黑暗森林》、《死神永生》。'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">INSERT</span> <span class="token keyword">INTO</span> <span class="token punctuation">`</span>tbl_book<span class="token punctuation">`</span> <span class="token keyword">VALUES</span> <span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">,</span> <span class="token string">'格林童话'</span><span class="token punctuation">,</span> <span class="token string">'童话'</span><span class="token punctuation">,</span> <span class="token string">'睡前故事。'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">INSERT</span> <span class="token keyword">INTO</span> <span class="token punctuation">`</span>tbl_book<span class="token punctuation">`</span> <span class="token keyword">VALUES</span> <span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">,</span> <span class="token string">'Spring 5设计模式'</span><span class="token punctuation">,</span> <span class="token string">'计算机理论'</span><span class="token punctuation">,</span> <span class="token string">'深入Spring源码剖析Spring源码中蕴含的10大设计模式'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">INSERT</span> <span class="token keyword">INTO</span> <span class="token punctuation">`</span>tbl_book<span class="token punctuation">`</span> <span class="token keyword">VALUES</span> <span class="token punctuation">(</span><span class="token number">4</span><span class="token punctuation">,</span> <span class="token string">'Spring MVC+ MyBatis开发从入门到项目实战'</span><span class="token punctuation">,</span> <span class="token string">'计算机理论'</span><span class="token punctuation">,</span> <span class="token string">'全方位解析面向Web应用的轻量级框架,带你成为Spring MVC开发高手'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">INSERT</span> <span class="token keyword">INTO</span> <span class="token punctuation">`</span>tbl_book<span class="token punctuation">`</span> <span class="token keyword">VALUES</span> <span class="token punctuation">(</span><span class="token number">5</span><span class="token punctuation">,</span> <span class="token string">'轻量级Java Web企业应用实战'</span><span class="token punctuation">,</span> <span class="token string">'计算机理论'</span><span class="token punctuation">,</span> <span class="token string">'源码级剖析Spring框架,适合已掌握Java基础的读者'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">INSERT</span> <span class="token keyword">INTO</span> <span class="token punctuation">`</span>tbl_book<span class="token punctuation">`</span> <span class="token keyword">VALUES</span> <span class="token punctuation">(</span><span class="token number">6</span><span class="token punctuation">,</span> <span class="token string">'Java核心技术卷|基础知识(原书第11版)'</span><span class="token punctuation">,</span> <span class="token string">'计算机理论'</span><span class="token punctuation">,</span> <span class="token string">'Core Java第11版，Jolt大奖获奖作品，针对Java SE9、10、 11全面更新'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">INSERT</span> <span class="token keyword">INTO</span> <span class="token punctuation">`</span>tbl_book<span class="token punctuation">`</span> <span class="token keyword">VALUES</span> <span class="token punctuation">(</span><span class="token number">7</span><span class="token punctuation">,</span> <span class="token string">'深入理解Java虚拟机'</span><span class="token punctuation">,</span> <span class="token string">'计算机理论'</span><span class="token punctuation">,</span> <span class="token string">'5个维度全面剖析JVM,面试知识点全覆盖'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">INSERT</span> <span class="token keyword">INTO</span> <span class="token punctuation">`</span>tbl_book<span class="token punctuation">`</span> <span class="token keyword">VALUES</span> <span class="token punctuation">(</span><span class="token number">8</span><span class="token punctuation">,</span> <span class="token string">'Java编程思想(第4版)'</span><span class="token punctuation">,</span> <span class="token string">'计算机理论'</span><span class="token punctuation">,</span> <span class="token string">'Java学习必读经典殿堂级著作!赢得了全球程序员的广泛赞誉'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">INSERT</span> <span class="token keyword">INTO</span> <span class="token punctuation">`</span>tbl_book<span class="token punctuation">`</span> <span class="token keyword">VALUES</span> <span class="token punctuation">(</span><span class="token number">9</span><span class="token punctuation">,</span> <span class="token string">'零基础学Java (全彩版)'</span><span class="token punctuation">,</span> <span class="token string">'计算机理论'</span><span class="token punctuation">,</span> <span class="token string">'零基础自学编程的入门]图书，由浅入深，详解Java语言的编程思想和核心技术'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">INSERT</span> <span class="token keyword">INTO</span> <span class="token punctuation">`</span>tbl_book<span class="token punctuation">`</span> <span class="token keyword">VALUES</span> <span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">,</span> <span class="token string">'直播就该这么做:主播高效沟通实战指南'</span><span class="token punctuation">,</span> <span class="token string">'市场营销'</span><span class="token punctuation">,</span> <span class="token string">'李子柒、李佳琦、薇娅成长为网红的秘密都在书中'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">INSERT</span> <span class="token keyword">INTO</span> <span class="token punctuation">`</span>tbl_book<span class="token punctuation">`</span> <span class="token keyword">VALUES</span> <span class="token punctuation">(</span><span class="token number">11</span><span class="token punctuation">,</span> <span class="token string">'直播销讲实战一本通'</span><span class="token punctuation">,</span> <span class="token string">'市场营销'</span><span class="token punctuation">,</span> <span class="token string">'和秋叶一起学系列网络营销书籍'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">INSERT</span> <span class="token keyword">INTO</span> <span class="token punctuation">`</span>tbl_book<span class="token punctuation">`</span> <span class="token keyword">VALUES</span> <span class="token punctuation">(</span><span class="token number">12</span><span class="token punctuation">,</span> <span class="token string">'直播带货:淘宝、天猫直播从新手到高手'</span><span class="token punctuation">,</span> <span class="token string">'市场营销'</span><span class="token punctuation">,</span> <span class="token string">'一本教你如何玩转直播的书， 10堂课轻松实现带货月入3W+'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">INSERT</span> <span class="token keyword">INTO</span> <span class="token punctuation">`</span>tbl_book<span class="token punctuation">`</span> <span class="token keyword">VALUES</span> <span class="token punctuation">(</span><span class="token number">13</span><span class="token punctuation">,</span> <span class="token string">'Spring实战第5版'</span><span class="token punctuation">,</span> <span class="token string">'计算机理论'</span><span class="token punctuation">,</span> <span class="token string">'Spring入门经典教程,深入理解Spring原理技术内幕'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">INSERT</span> <span class="token keyword">INTO</span> <span class="token punctuation">`</span>tbl_book<span class="token punctuation">`</span> <span class="token keyword">VALUES</span> <span class="token punctuation">(</span><span class="token number">14</span><span class="token punctuation">,</span> <span class="token string">'Spring 5核心原理与30个类手写实战'</span><span class="token punctuation">,</span> <span class="token string">'计算机理论'</span><span class="token punctuation">,</span> <span class="token string">'十年沉淀之作，写Spring精华思想'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">SET</span> FOREIGN_KEY_CHECKS <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>实体类</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Book</span> <span class="token punctuation">{</span>    <span class="token keyword">private</span> Integer id<span class="token punctuation">;</span>    <span class="token keyword">private</span> String type<span class="token punctuation">;</span>    <span class="token keyword">private</span> String name<span class="token punctuation">;</span>    <span class="token keyword">private</span> String description<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>映射接口（Dao）</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Mapper</span><span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">BookDao</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Select</span><span class="token punctuation">(</span><span class="token string">"select * from tbl_book where id = #{id}"</span><span class="token punctuation">)</span>    <span class="token keyword">public</span> Book <span class="token function">getById</span><span class="token punctuation">(</span>Integer id<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>测试类</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@SpringBootTest</span><span class="token keyword">class</span> <span class="token class-name">Springboot05MybatisApplicationTests</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Autowired</span>    <span class="token keyword">private</span> BookDao bookDao<span class="token punctuation">;</span>    <span class="token annotation punctuation">@Test</span>    <span class="token keyword">void</span> <span class="token function">contextLoads</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>bookDao<span class="token punctuation">.</span><span class="token function">getById</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>总结</strong></p><ol><li><p>整合操作需要勾选MyBatis技术，也就是导入MyBatis对应的starter</p></li><li><p>数据库连接相关信息转换成配置</p></li><li><p>数据库SQL映射需要添加@Mapper被容器识别到</p></li><li><p>MySQL 8.X驱动强制要求设置时区</p><ul><li>修改url，添加serverTimezone设定</li><li>修改MySQL数据库配置：修改mysql中的配置文件mysql.ini，在mysqld项中添加default-time-zone&#x3D;+8:00</li></ul></li><li><p>驱动类过时，提醒更换为com.mysql.cj.jdbc.Driver</p></li></ol><h2 id="3-整合MyBatis-Plus"><a href="#3-整合MyBatis-Plus" class="headerlink" title="3.整合MyBatis-Plus"></a>3.整合MyBatis-Plus</h2><p><strong>步骤①</strong>：导入对应的 starter 和数据库驱动</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>com.baomidou<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>mybatis-plus-boot-starter<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>3.4.3<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>mysql<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>mysql-connector-java<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>scope</span><span class="token punctuation">></span></span>runtime<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>scope</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤②</strong>：配置数据源相关信息</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">spring</span><span class="token punctuation">:</span>  <span class="token key atrule">datasource</span><span class="token punctuation">:</span>    <span class="token key atrule">driver-class-name</span><span class="token punctuation">:</span> com.mysql.cj.jdbc.Driver    <span class="token key atrule">url</span><span class="token punctuation">:</span> jdbc<span class="token punctuation">:</span>mysql<span class="token punctuation">:</span>//localhost<span class="token punctuation">:</span>3306/ssm_db    <span class="token key atrule">username</span><span class="token punctuation">:</span> root    <span class="token key atrule">password</span><span class="token punctuation">:</span> root<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤③</strong>：配置表名的通用前缀名</p><p>数据库的表名定义规则是tbl_模块名称，为了能和实体类相对应，需要设置所有表名的通用前缀名</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">mybatis-plus</span><span class="token punctuation">:</span>  <span class="token key atrule">global-config</span><span class="token punctuation">:</span>    <span class="token key atrule">db-config</span><span class="token punctuation">:</span>      <span class="token key atrule">table-prefix</span><span class="token punctuation">:</span> tbl_<span class="token comment" spellcheck="true">#设置所有表的通用前缀名称为tbl_</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤④</strong>：配置运行日志</p><p>使用MP后开发者不需要书写SQL语句了，这样程序运行的时候总有一种感觉，一切的一切都是黑盒的，作为开发者我们啥也不知道就完了。如果程序正常运行还好，如果报错了，这个时候就很崩溃，你甚至都不知道从何下手，因为传递参数、封装SQL语句这些操作完全不是你干预开发出来的，所以查看执行期运行的SQL语句就成为当务之急。SpringBoot整合MP的时候充分考虑到了这点，通过配置的形式就可以查阅执行期SQL语句，配置如下</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">mybatis-plus</span><span class="token punctuation">:</span>  <span class="token key atrule">global-config</span><span class="token punctuation">:</span>    <span class="token key atrule">db-config</span><span class="token punctuation">:</span>      <span class="token key atrule">table-prefix</span><span class="token punctuation">:</span> tbl_      <span class="token key atrule">id-type</span><span class="token punctuation">:</span> auto  <span class="token key atrule">configuration</span><span class="token punctuation">:</span>    <span class="token key atrule">log-impl</span><span class="token punctuation">:</span> org.apache.ibatis.logging.stdout.StdOutImpl <span class="token comment" spellcheck="true">#配置日志</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>测试</strong></p><ul><li>实体类</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Book</span> <span class="token punctuation">{</span>    <span class="token keyword">private</span> Integer id<span class="token punctuation">;</span>    <span class="token keyword">private</span> String type<span class="token punctuation">;</span>    <span class="token keyword">private</span> String name<span class="token punctuation">;</span>    <span class="token keyword">private</span> String description<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>映射接口（Dao）</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Mapper</span><span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">BookDao</span> <span class="token keyword">extends</span> <span class="token class-name">BaseMapper</span><span class="token operator">&lt;</span>Book<span class="token operator">></span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>核心在于Dao接口继承了一个BaseMapper的接口，这个接口中帮助开发者预定了若干个常用的API接口，简化了通用API接口的开发工作。</p><ul><li>测试类</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@SpringBootTest</span><span class="token keyword">class</span> <span class="token class-name">SpringbootApplicationTests</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Autowired</span>    <span class="token keyword">private</span> BookDao bookDao<span class="token punctuation">;</span>    <span class="token annotation punctuation">@Test</span>    <span class="token keyword">void</span> <span class="token function">contextLoads</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>bookDao<span class="token punctuation">.</span><span class="token function">selectById</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>bookDao<span class="token punctuation">.</span><span class="token function">selectList</span><span class="token punctuation">(</span>null<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>总结</strong></p><ol><li>手工添加MyBatis-Plus对应的starter</li><li>数据层接口使用BaseMapper简化开发</li><li>借助MyBatis-Plus日志可以查阅执行SQL语句</li><li>需要使用的第三方技术无法通过勾选确定时，需要手工添加坐标</li></ol><h2 id="4-整合Durid"><a href="#4-整合Durid" class="headerlink" title="4.整合Durid"></a>4.整合Durid</h2><p>前面整合 MyBatis 和 MyBatis-Plus 的时候，使用的数据源对象都是SpringBoot默认的数据源对象 HiKari，下面我们手工控制一下，自己指定了一个数据源对象 Druid。</p><p><strong>步骤①</strong>：导入对应的starter</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependencies</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>com.alibaba<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>druid-spring-boot-starter<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>1.2.6<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependencies</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤②</strong>：修改配置</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">spring</span><span class="token punctuation">:</span>  <span class="token key atrule">datasource</span><span class="token punctuation">:</span>    <span class="token key atrule">druid</span><span class="token punctuation">:</span>      <span class="token key atrule">driver-class-name</span><span class="token punctuation">:</span> com.mysql.cj.jdbc.Driver      <span class="token key atrule">url</span><span class="token punctuation">:</span> jdbc<span class="token punctuation">:</span>mysql<span class="token punctuation">:</span>//localhost<span class="token punctuation">:</span>3306/ssm_db<span class="token punctuation">?</span>serverTimezone=UTC      <span class="token key atrule">username</span><span class="token punctuation">:</span> root      <span class="token key atrule">password</span><span class="token punctuation">:</span> root      <span class="token comment" spellcheck="true">#除了这4个常规配置外，还有druid专用的其他配置。通过提示功能可以打开druid相关的配置查阅</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>总结</strong></p><ol><li>整合Druid需要导入Druid对应的starter</li><li>根据Druid提供的配置方式进行配置</li></ol><h2 id="5-整合Redis"><a href="#5-整合Redis" class="headerlink" title="5.整合Redis"></a>5.整合Redis</h2><p><strong>步骤①</strong>：导入对应的starter 和依赖</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework.boot<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-boot-starter-data-redis<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span class="token comment" spellcheck="true">&lt;!--连接池依赖--></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.apache.commons<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>commons-pool2<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>2.6.0<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤②</strong>：修改配置</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">spring</span><span class="token punctuation">:</span>  <span class="token key atrule">redis</span><span class="token punctuation">:</span>    <span class="token key atrule">host</span><span class="token punctuation">:</span> 127.0.0.1 <span class="token comment" spellcheck="true">#指定redis所在的host</span>    <span class="token key atrule">port</span><span class="token punctuation">:</span> <span class="token number">6379  </span><span class="token comment" spellcheck="true">#指定redis的端口</span>    <span class="token key atrule">password</span><span class="token punctuation">:</span> <span class="token number">123456  </span><span class="token comment" spellcheck="true">#设置redis密码</span>    <span class="token key atrule">lettuce</span><span class="token punctuation">:</span>      <span class="token key atrule">pool</span><span class="token punctuation">:</span>        <span class="token key atrule">max-active</span><span class="token punctuation">:</span> <span class="token number">8 </span><span class="token comment" spellcheck="true">#最大连接数</span>        <span class="token key atrule">max-idle</span><span class="token punctuation">:</span> <span class="token number">8 </span><span class="token comment" spellcheck="true">#最大空闲数</span>        <span class="token key atrule">min-idle</span><span class="token punctuation">:</span> <span class="token number">0 </span><span class="token comment" spellcheck="true">#最小空闲数</span>        <span class="token key atrule">max-wait</span><span class="token punctuation">:</span> 100ms <span class="token comment" spellcheck="true">#连接等待时间</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>测试</strong></p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@SpringBootTest</span><span class="token keyword">class</span> <span class="token class-name">SpringRedisApplicationTests</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Resource</span>    <span class="token keyword">private</span> RedisTemplate redisTemplate<span class="token punctuation">;</span>    <span class="token annotation punctuation">@Test</span>    <span class="token keyword">void</span> <span class="token function">testString</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 1.通过RedisTemplate获取操作String类型的ValueOperations对象</span>        ValueOperations ops <span class="token operator">=</span> redisTemplate<span class="token punctuation">.</span><span class="token function">opsForValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 2.插入一条数据</span>        ops<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span><span class="token string">"name"</span><span class="token punctuation">,</span><span class="token string">"jianjian"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 3.获取数据</span>        String name <span class="token operator">=</span> <span class="token punctuation">(</span>String<span class="token punctuation">)</span> ops<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">"name"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"name = "</span> <span class="token operator">+</span> name<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h1 id="五、整合案例"><a href="#五、整合案例" class="headerlink" title="五、整合案例"></a>五、整合案例</h1><blockquote><p>本章将实现一个基于 SpringBoot -Vue 的 CRUD 整合 Demo</p><p>项目地址：<a href="https://github.com/jwt1399/SpringBoot-Vue-CRUD">https://github.com/jwt1399/SpringBoot-Vue-CRUD</a></p></blockquote><h2 id="0-项目简介"><a href="#0-项目简介" class="headerlink" title="0.项目简介"></a>0.项目简介</h2><h3 id="①-项目层次"><a href="#①-项目层次" class="headerlink" title="①.项目层次"></a>①.项目层次</h3><ul><li><strong>entity层</strong></li></ul><p>entity层即数据库实体层，也称为 POJO 层（Plain Ordinary Java Object，即简单普通的java对象）或 model 层。 entity层的作用为存放实体类Bean。 一般数据库一张表对应一个实体类，类属性同表字段一一对应。</p><ul><li><strong>dao层</strong></li></ul><p>dao层即数据持久层，也称为mapper层。 dao层的作用为访问数据库，向数据库发送sql语句，完成数据的增删改查任务。</p><ul><li><strong>service层</strong></li></ul><p>service层即业务逻辑层。  service层的作用为完成功能设计。  service层调用dao层接口，接收dao层返回的数据，完成项目的基本功能设计。</p><ul><li><strong>controller层</strong></li></ul><p>controller层即控制层。  controller层的功能为请求和响应控制。  controller层负责前后端交互，接受前端请求，调用service层，接收service层返回的数据，最后返回具体的页面和数据到客户端。</p><p><img src="https://img.jwt1399.top/img/202209082324008"></p><h3 id="②-相关技术"><a href="#②-相关技术" class="headerlink" title="②.相关技术"></a>②.相关技术</h3><ul><li>Bean开发————使用<code>Lombok</code>快速制作实体类</li><li>Dao开发————整合<code>MyBatis-Plus</code>，制作数据层测试</li><li>Service开发————基于<code>MyBatis-Plus</code>进行增量开发，制作业务层测试类</li><li>Controller开发————基于<code>Restful</code>开发，使用<code>PostMan</code>测试接口功能</li><li>页面开发————基于<code>VUE+ElementUI</code>制作，前后端联调，页面数据处理，页面消息处理</li></ul><h3 id="③-项目展示"><a href="#③-项目展示" class="headerlink" title="③.项目展示"></a>③.项目展示</h3><h4 id="主页面"><a href="#主页面" class="headerlink" title="主页面"></a>主页面</h4><p><img src="https://img.jwt1399.top/img/202209080000584.png"></p><h4 id="增-x2F-改"><a href="#增-x2F-改" class="headerlink" title="增&#x2F;改"></a>增&#x2F;改</h4><p><img src="https://img.jwt1399.top/img/202209080001120.png"></p><h4 id="删除"><a href="#删除" class="headerlink" title="删除"></a>删除</h4><p><img src="https://img.jwt1399.top/img/202209080001905.png"></p><h4 id="查询"><a href="#查询" class="headerlink" title="查询"></a>查询</h4><p><img src="https://img.jwt1399.top/img/202209080001485.png"></p><h2 id="1-项目创建"><a href="#1-项目创建" class="headerlink" title="1.项目创建"></a>1.项目创建</h2><p>创建新项目，加载要使用的技术对应的starter，修改配置文件格式为yml格式，并把web访问端口先设置成80。</p><p><strong>pom.xml</strong></p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework.boot<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-boot-starter-web<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework.boot<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-boot-starter-test<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>scope</span><span class="token punctuation">></span></span>test<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>scope</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>application.yml</strong></p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">server</span><span class="token punctuation">:</span>  <span class="token key atrule">port</span><span class="token punctuation">:</span> <span class="token number">80</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><h2 id="2-实体层开发"><a href="#2-实体层开发" class="headerlink" title="2.实体层开发"></a>2.实体层开发</h2><p><strong>数据表</strong></p><pre class="line-numbers language-sql"><code class="language-sql"><span class="token comment" spellcheck="true">-- ----------------------------</span><span class="token comment" spellcheck="true">-- Table structure for tbl_book</span><span class="token comment" spellcheck="true">-- ----------------------------</span><span class="token keyword">DROP</span> <span class="token keyword">TABLE</span> <span class="token keyword">IF</span> <span class="token keyword">EXISTS</span> <span class="token punctuation">`</span>tbl_book<span class="token punctuation">`</span><span class="token punctuation">;</span><span class="token keyword">CREATE</span> <span class="token keyword">TABLE</span> <span class="token punctuation">`</span>tbl_book<span class="token punctuation">`</span>  <span class="token punctuation">(</span>  <span class="token punctuation">`</span>id<span class="token punctuation">`</span> <span class="token keyword">int</span><span class="token punctuation">(</span><span class="token number">11</span><span class="token punctuation">)</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">AUTO_INCREMENT</span><span class="token punctuation">,</span>  <span class="token punctuation">`</span><span class="token keyword">type</span><span class="token punctuation">`</span> <span class="token keyword">varchar</span><span class="token punctuation">(</span><span class="token number">20</span><span class="token punctuation">)</span> <span class="token keyword">CHARACTER SET</span> utf8 <span class="token keyword">COLLATE</span> utf8_general_ci <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span>  <span class="token punctuation">`</span>name<span class="token punctuation">`</span> <span class="token keyword">varchar</span><span class="token punctuation">(</span><span class="token number">50</span><span class="token punctuation">)</span> <span class="token keyword">CHARACTER SET</span> utf8 <span class="token keyword">COLLATE</span> utf8_general_ci <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span>  <span class="token punctuation">`</span>description<span class="token punctuation">`</span> <span class="token keyword">varchar</span><span class="token punctuation">(</span><span class="token number">255</span><span class="token punctuation">)</span> <span class="token keyword">CHARACTER SET</span> utf8 <span class="token keyword">COLLATE</span> utf8_general_ci <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span>  <span class="token keyword">PRIMARY</span> <span class="token keyword">KEY</span> <span class="token punctuation">(</span><span class="token punctuation">`</span>id<span class="token punctuation">`</span><span class="token punctuation">)</span> <span class="token keyword">USING</span> <span class="token keyword">BTREE</span><span class="token punctuation">)</span> <span class="token keyword">ENGINE</span> <span class="token operator">=</span> <span class="token keyword">InnoDB</span> <span class="token keyword">AUTO_INCREMENT</span> <span class="token operator">=</span> <span class="token number">51</span> <span class="token keyword">CHARACTER SET</span> <span class="token operator">=</span> utf8 <span class="token keyword">COLLATE</span> <span class="token operator">=</span> utf8_general_ci ROW_FORMAT <span class="token operator">=</span> Dynamic<span class="token punctuation">;</span><span class="token comment" spellcheck="true">-- ----------------------------</span><span class="token comment" spellcheck="true">-- Records of tbl_book</span><span class="token comment" spellcheck="true">-- ----------------------------</span><span class="token keyword">INSERT</span> <span class="token keyword">INTO</span> <span class="token punctuation">`</span>tbl_book<span class="token punctuation">`</span> <span class="token keyword">VALUES</span> <span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token string">'计算机理论'</span><span class="token punctuation">,</span> <span class="token string">'Spring实战 第5版'</span><span class="token punctuation">,</span> <span class="token string">'Spring入门经典教程，深入理解Spring原理技术内幕'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">INSERT</span> <span class="token keyword">INTO</span> <span class="token punctuation">`</span>tbl_book<span class="token punctuation">`</span> <span class="token keyword">VALUES</span> <span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">,</span> <span class="token string">'计算机理论'</span><span class="token punctuation">,</span> <span class="token string">'Spring 5核心原理与30个类手写实战'</span><span class="token punctuation">,</span> <span class="token string">'十年沉淀之作，手写Spring精华思想'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">INSERT</span> <span class="token keyword">INTO</span> <span class="token punctuation">`</span>tbl_book<span class="token punctuation">`</span> <span class="token keyword">VALUES</span> <span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">,</span> <span class="token string">'计算机理论'</span><span class="token punctuation">,</span> <span class="token string">'Spring 5 设计模式'</span><span class="token punctuation">,</span> <span class="token string">'深入Spring源码剖析Spring源码中蕴含的10大设计模式'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">INSERT</span> <span class="token keyword">INTO</span> <span class="token punctuation">`</span>tbl_book<span class="token punctuation">`</span> <span class="token keyword">VALUES</span> <span class="token punctuation">(</span><span class="token number">4</span><span class="token punctuation">,</span> <span class="token string">'计算机理论'</span><span class="token punctuation">,</span> <span class="token string">'Spring MVC+MyBatis开发从入门到项目实战'</span><span class="token punctuation">,</span> <span class="token string">'全方位解析面向Web应用的轻量级框架，带你成为Spring MVC开发高手'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">INSERT</span> <span class="token keyword">INTO</span> <span class="token punctuation">`</span>tbl_book<span class="token punctuation">`</span> <span class="token keyword">VALUES</span> <span class="token punctuation">(</span><span class="token number">5</span><span class="token punctuation">,</span> <span class="token string">'计算机理论'</span><span class="token punctuation">,</span> <span class="token string">'轻量级Java Web企业应用实战'</span><span class="token punctuation">,</span> <span class="token string">'源码级剖析Spring框架，适合已掌握Java基础的读者'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">INSERT</span> <span class="token keyword">INTO</span> <span class="token punctuation">`</span>tbl_book<span class="token punctuation">`</span> <span class="token keyword">VALUES</span> <span class="token punctuation">(</span><span class="token number">6</span><span class="token punctuation">,</span> <span class="token string">'计算机理论'</span><span class="token punctuation">,</span> <span class="token string">'Java核心技术 卷I 基础知识（原书第11版）'</span><span class="token punctuation">,</span> <span class="token string">'Core Java 第11版，Jolt大奖获奖作品，针对Java SE9、10、11全面更新'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">INSERT</span> <span class="token keyword">INTO</span> <span class="token punctuation">`</span>tbl_book<span class="token punctuation">`</span> <span class="token keyword">VALUES</span> <span class="token punctuation">(</span><span class="token number">7</span><span class="token punctuation">,</span> <span class="token string">'计算机理论'</span><span class="token punctuation">,</span> <span class="token string">'深入理解Java虚拟机'</span><span class="token punctuation">,</span> <span class="token string">'5个维度全面剖析JVM，大厂面试知识点全覆盖'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">INSERT</span> <span class="token keyword">INTO</span> <span class="token punctuation">`</span>tbl_book<span class="token punctuation">`</span> <span class="token keyword">VALUES</span> <span class="token punctuation">(</span><span class="token number">8</span><span class="token punctuation">,</span> <span class="token string">'计算机理论'</span><span class="token punctuation">,</span> <span class="token string">'Java编程思想（第4版）'</span><span class="token punctuation">,</span> <span class="token string">'Java学习必读经典,殿堂级著作！赢得了全球程序员的广泛赞誉'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">INSERT</span> <span class="token keyword">INTO</span> <span class="token punctuation">`</span>tbl_book<span class="token punctuation">`</span> <span class="token keyword">VALUES</span> <span class="token punctuation">(</span><span class="token number">9</span><span class="token punctuation">,</span> <span class="token string">'计算机理论'</span><span class="token punctuation">,</span> <span class="token string">'零基础学Java（全彩版）'</span><span class="token punctuation">,</span> <span class="token string">'零基础自学编程的入门图书，由浅入深，详解Java语言的编程思想和核心技术'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">INSERT</span> <span class="token keyword">INTO</span> <span class="token punctuation">`</span>tbl_book<span class="token punctuation">`</span> <span class="token keyword">VALUES</span> <span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">,</span> <span class="token string">'市场营销'</span><span class="token punctuation">,</span> <span class="token string">'直播就该这么做：主播高效沟通实战指南'</span><span class="token punctuation">,</span> <span class="token string">'李子柒、李佳琦、薇娅成长为网红的秘密都在书中'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">INSERT</span> <span class="token keyword">INTO</span> <span class="token punctuation">`</span>tbl_book<span class="token punctuation">`</span> <span class="token keyword">VALUES</span> <span class="token punctuation">(</span><span class="token number">11</span><span class="token punctuation">,</span> <span class="token string">'市场营销'</span><span class="token punctuation">,</span> <span class="token string">'直播销讲实战一本通'</span><span class="token punctuation">,</span> <span class="token string">'和秋叶一起学系列网络营销书籍'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">INSERT</span> <span class="token keyword">INTO</span> <span class="token punctuation">`</span>tbl_book<span class="token punctuation">`</span> <span class="token keyword">VALUES</span> <span class="token punctuation">(</span><span class="token number">12</span><span class="token punctuation">,</span> <span class="token string">'市场营销'</span><span class="token punctuation">,</span> <span class="token string">'直播带货：淘宝、天猫直播从新手到高手'</span><span class="token punctuation">,</span> <span class="token string">'一本教你如何玩转直播的书，10堂课轻松实现带货月入3W+'</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>实体类</strong></p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Book</span> <span class="token punctuation">{</span>    <span class="token keyword">private</span> Integer id<span class="token punctuation">;</span>    <span class="token keyword">private</span> String type<span class="token punctuation">;</span>    <span class="token keyword">private</span> String name<span class="token punctuation">;</span>    <span class="token keyword">private</span> String description<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>实体类的开发可以手工生成get&#x2F;set方法，然后覆盖toString()方法。不过这一套操作书写很繁琐，可以使用 <code>Lombok</code>简化JavaBean开发。</p><p>引入 Lombok，用注解代替构造器、getter&#x2F;setter、toString()等代码，新版 IDEA 已集成 Lombok 插件，无需下载</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.projectlombok<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>lombok<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><ul><li><p>@Data ： 注在类上，提供类的get、set、equals、hashCode、toString等方法</p></li><li><p>@AllArgsConstructor ：注在类上，提供类的全参构造</p></li><li><p>@NoArgsConstructor ：注在类上，提供类的无参构造</p></li><li><p>@Setter ：注在属性上，提供 set 方法</p></li><li><p>@Getter ：注在属性上，提供 get 方法</p></li><li><p>@EqualsAndHashCode ：注在类上，提供对应的 equals 和 hashCode 方法</p></li><li><p>@Log4j&#x2F;@Slf4j ：注在类上，提供对应的 Logger 对象，变量名为 log</p></li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">//简化JavaBean开发</span><span class="token annotation punctuation">@NoArgsConstructor</span> <span class="token comment" spellcheck="true">//无参构造</span><span class="token annotation punctuation">@AllArgsConstructor</span> <span class="token comment" spellcheck="true">//有参构造</span><span class="token annotation punctuation">@Data</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Book</span> <span class="token punctuation">{</span>    <span class="token keyword">private</span> Integer id<span class="token punctuation">;</span>    <span class="token keyword">private</span> String type<span class="token punctuation">;</span>    <span class="token keyword">private</span> String name<span class="token punctuation">;</span>    <span class="token keyword">private</span> String description<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>Lombok 还可以简化日志开发，例如下面代码</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Slf4j</span><span class="token annotation punctuation">@RestController</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">HelloController</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@RequestMapping</span><span class="token punctuation">(</span><span class="token string">"/hello"</span><span class="token punctuation">)</span>    <span class="token keyword">public</span> String <span class="token function">handle</span><span class="token punctuation">(</span><span class="token annotation punctuation">@RequestParam</span><span class="token punctuation">(</span><span class="token string">"name"</span><span class="token punctuation">)</span> String name<span class="token punctuation">)</span><span class="token punctuation">{</span>        log<span class="token punctuation">.</span><span class="token function">info</span><span class="token punctuation">(</span><span class="token string">"请求进来了...."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> <span class="token string">"Hello, Spring Boot 2!"</span><span class="token operator">+</span><span class="token string">"你好："</span><span class="token operator">+</span>name<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="3-数据层开发"><a href="#3-数据层开发" class="headerlink" title="3.数据层开发"></a>3.数据层开发</h2><h3 id="①基础CRUD"><a href="#①基础CRUD" class="headerlink" title="①基础CRUD"></a>①基础CRUD</h3><p><strong>步骤①</strong>：导入MyBatisPlus与Druid对应的starter，当然mysql的驱动不能少</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>com.baomidou<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>mybatis-plus-boot-starter<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>3.4.3<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>com.alibaba<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>druid-spring-boot-starter<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>1.2.6<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>mysql<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>mysql-connector-java<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>scope</span><span class="token punctuation">></span></span>runtime<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>scope</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤②</strong>：配置数据库连接相关的数据源配置</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">spring</span><span class="token punctuation">:</span>  <span class="token key atrule">datasource</span><span class="token punctuation">:</span>    <span class="token key atrule">druid</span><span class="token punctuation">:</span>      <span class="token key atrule">driver-class-name</span><span class="token punctuation">:</span> com.mysql.cj.jdbc.Driver      <span class="token key atrule">url</span><span class="token punctuation">:</span> jdbc<span class="token punctuation">:</span>mysql<span class="token punctuation">:</span>//localhost<span class="token punctuation">:</span>3306/ssm_db<span class="token punctuation">?</span>serverTimezone=Asia/Shanghai      <span class="token key atrule">username</span><span class="token punctuation">:</span> root      <span class="token key atrule">password</span><span class="token punctuation">:</span> root<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤③</strong>：配置MP相关配置</p><ul><li>表名通用前缀</li><li>主键生成策略<ul><li>MP技术默认的主键生成策略为雪花算法，生成的主键ID长度较大，和目前的数据库设定规则不相符，需要配置一下使MP使用数据库的主键生成策略</li></ul></li><li>配置MP日志<ul><li>设置日志输出方式为标准输出即可查阅SQL执行日志</li></ul></li></ul><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">mybatis-plus</span><span class="token punctuation">:</span>  <span class="token key atrule">global-config</span><span class="token punctuation">:</span>    <span class="token key atrule">db-config</span><span class="token punctuation">:</span>      <span class="token key atrule">table-prefix</span><span class="token punctuation">:</span> tbl_<span class="token comment" spellcheck="true">#设置表名通用前缀</span>      <span class="token key atrule">id-type</span><span class="token punctuation">:</span> auto<span class="token comment" spellcheck="true">#设置主键id字段的生成策略为参照数据库设定的策略，当前数据库设置id生成策略为自增</span>  <span class="token key atrule">configuration</span><span class="token punctuation">:</span>    <span class="token key atrule">log-impl</span><span class="token punctuation">:</span> org.apache.ibatis.logging.stdout.StdOutImpl  <span class="token comment" spellcheck="true">#配置标准输出日志</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤④</strong>：使用MP的标准通用接口BaseMapper加速开发，别忘了@Mapper和泛型的指定</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Mapper</span><span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">BookDao</span> <span class="token keyword">extends</span> <span class="token class-name">BaseMapper</span><span class="token operator">&lt;</span>Book<span class="token operator">></span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p><strong>步骤⑤</strong>：制作测试类测试结果</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">package</span> com<span class="token punctuation">.</span>jianjian<span class="token punctuation">.</span>dao<span class="token punctuation">;</span><span class="token annotation punctuation">@SpringBootTest</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">BookDaoTests</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Autowired</span>    <span class="token keyword">private</span> BookDao bookDao<span class="token punctuation">;</span>    <span class="token annotation punctuation">@Test</span>    <span class="token keyword">void</span> <span class="token function">testGetById</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>bookDao<span class="token punctuation">.</span><span class="token function">selectById</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@Test</span>    <span class="token keyword">void</span> <span class="token function">testSave</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>        Book book <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Book</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        book<span class="token punctuation">.</span><span class="token function">setType</span><span class="token punctuation">(</span><span class="token string">"测试数据123"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        book<span class="token punctuation">.</span><span class="token function">setName</span><span class="token punctuation">(</span><span class="token string">"测试数据123"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        book<span class="token punctuation">.</span><span class="token function">setDescription</span><span class="token punctuation">(</span><span class="token string">"测试数据123"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        bookDao<span class="token punctuation">.</span><span class="token function">insert</span><span class="token punctuation">(</span>book<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@Test</span>    <span class="token keyword">void</span> <span class="token function">testUpdate</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>        Book book <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Book</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        book<span class="token punctuation">.</span><span class="token function">setId</span><span class="token punctuation">(</span><span class="token number">17</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        book<span class="token punctuation">.</span><span class="token function">setType</span><span class="token punctuation">(</span><span class="token string">"测试数据abcdefg"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        book<span class="token punctuation">.</span><span class="token function">setName</span><span class="token punctuation">(</span><span class="token string">"测试数据123"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        book<span class="token punctuation">.</span><span class="token function">setDescription</span><span class="token punctuation">(</span><span class="token string">"测试数据123"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        bookDao<span class="token punctuation">.</span><span class="token function">updateById</span><span class="token punctuation">(</span>book<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@Test</span>    <span class="token keyword">void</span> <span class="token function">testDelete</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>        bookDao<span class="token punctuation">.</span><span class="token function">deleteById</span><span class="token punctuation">(</span><span class="token number">16</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@Test</span>    <span class="token keyword">void</span> <span class="token function">testGetAll</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>        bookDao<span class="token punctuation">.</span><span class="token function">selectList</span><span class="token punctuation">(</span>null<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>总结</strong></p><ol><li>手工导入starter坐标（2个），mysql驱动（1个）</li><li>配置数据源与MyBatisPlus对应的配置</li><li>配置MP相关配置</li><li>开发Dao接口（继承BaseMapper）</li><li>制作测试类测试Dao功能是否有效</li></ol><h3 id="②分页功能"><a href="#②分页功能" class="headerlink" title="②分页功能"></a>②分页功能</h3><p>MyBatis-Plus自带分页插件，只要需要<strong>定义MP拦截器并将其设置为Spring管控的bean</strong>即可实现分页功能</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Configuration</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">MPConfig</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Bean</span>    <span class="token keyword">public</span> MybatisPlusInterceptor <span class="token function">mybatisPlusInterceptor</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">//创建MP的拦截器栈</span>        MybatisPlusInterceptor interceptor <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">MybatisPlusInterceptor</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">//初始化了分页拦截器，并添加到拦截器栈中</span>        <span class="token comment" spellcheck="true">//如果后期开发其他功能，需要添加全新的拦截器，按照此行的格式继续add进去新的拦截器就可以了。</span>        interceptor<span class="token punctuation">.</span><span class="token function">addInnerInterceptor</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">PaginationInnerInterceptor</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> interceptor<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>MP提供的分页操作API如下</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Test</span><span class="token keyword">void</span> <span class="token function">testGetPage</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>    IPage page <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Page</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">,</span><span class="token number">5</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    bookDao<span class="token punctuation">.</span><span class="token function">selectPage</span><span class="token punctuation">(</span>page<span class="token punctuation">,</span> null<span class="token punctuation">)</span><span class="token punctuation">;</span>    System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>page<span class="token punctuation">.</span><span class="token function">getCurrent</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment" spellcheck="true">//当前页码值</span>    System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>page<span class="token punctuation">.</span><span class="token function">getSize</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>   <span class="token comment" spellcheck="true">//每页显示数</span>    System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>page<span class="token punctuation">.</span><span class="token function">getTotal</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment" spellcheck="true">//数据总量</span>    System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>page<span class="token punctuation">.</span><span class="token function">getPages</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//总页数</span>    System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>page<span class="token punctuation">.</span><span class="token function">getRecords</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//详细数据</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>其中<code>selectPage</code>方法需要传入一个封装分页数据的<code>IPage</code>对象，可以通过new的形式创建这个对象。创建此对象时就需要指定分页的两个基本数据</p><ul><li>当前显示第几页</li><li>每页显示几条数据</li></ul><p>可以通过创建Page对象时利用构造方法初始化这两个数据</p><pre class="line-numbers language-java"><code class="language-java">IPage page <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Page</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">,</span><span class="token number">5</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>将该对象传入到查询方法selectPage后，可以得到查询结果，但是我们会发现当前操作查询结果返回值仍然是一个IPage对象，这又是怎么回事？</p><pre class="line-numbers language-java"><code class="language-java">IPage page <span class="token operator">=</span> bookDao<span class="token punctuation">.</span><span class="token function">selectPage</span><span class="token punctuation">(</span>page<span class="token punctuation">,</span> null<span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>原来这个IPage对象中封装了若干个数据，而查询的结果作为IPage对象封装的一个数据存在的，可以理解为查询结果得到后，又塞到了这个IPage对象中，其实还是为了高度的封装。</p><p><strong>总结</strong></p><ol><li>分页操作依赖MyBatisPlus分页拦截器实现功能</li><li>使用IPage封装分页数据</li></ol><h3 id="③条件查询"><a href="#③条件查询" class="headerlink" title="③条件查询"></a>③条件查询</h3><p>以往我们写条件查询要自己动态拼写复杂的SQL语句，现在简单了，MP将这些操作都制作成API接口，调用一个又一个的方法就可以实现各种套件的拼装。</p><p>下面的操作就是执行一个模糊匹配对应的操作，由like条件书写变为了like方法的调用</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Test</span><span class="token keyword">void</span> <span class="token function">testGetBy</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">//封装查询条件的对象</span>    QueryWrapper<span class="token operator">&lt;</span>Book<span class="token operator">></span> qw <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">QueryWrapper</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">//查询条件</span>    qw<span class="token punctuation">.</span><span class="token function">like</span><span class="token punctuation">(</span><span class="token string">"name"</span><span class="token punctuation">,</span><span class="token string">"Spring"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    bookDao<span class="token punctuation">.</span><span class="token function">selectList</span><span class="token punctuation">(</span>qw<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>由于属性字段名的书写存在着安全隐患，比如查询字段name，当前是以字符串的形态书写的，万一写错，编译器还没有办法发现，只能将问题抛到运行器通过异常堆栈告诉开发者，不太友好。</p><p>MP针对字段检查进行了功能升级，全面支持Lambda表达式。由QueryWrapper对象升级为LambdaQueryWrapper对象，这下就解决了上述问题的出现</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Test</span><span class="token keyword">void</span> <span class="token function">testGetBy2</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>    String name <span class="token operator">=</span> <span class="token string">"1"</span><span class="token punctuation">;</span>    LambdaQueryWrapper<span class="token operator">&lt;</span>Book<span class="token operator">></span> lqw <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">LambdaQueryWrapper</span><span class="token operator">&lt;</span>Book<span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    lqw<span class="token punctuation">.</span><span class="token function">like</span><span class="token punctuation">(</span>Book<span class="token operator">:</span><span class="token operator">:</span>getName<span class="token punctuation">,</span><span class="token string">"Spring"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    bookDao<span class="token punctuation">.</span><span class="token function">selectList</span><span class="token punctuation">(</span>lqw<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>为了便于开发者动态拼写SQL，防止将null数据作为条件使用，MP还提供了动态拼装SQL的快捷书写方式</p><pre class="line-numbers language-java"><code class="language-java">    <span class="token annotation punctuation">@Test</span>    <span class="token keyword">void</span> <span class="token function">testGetBy3</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>        String name <span class="token operator">=</span> <span class="token string">"Spring"</span><span class="token punctuation">;</span>        LambdaQueryWrapper<span class="token operator">&lt;</span>Book<span class="token operator">></span> lqw <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">LambdaQueryWrapper</span><span class="token operator">&lt;</span>Book<span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">//if(name != null) lqw.like(Book::getName,name);//方式一：JAVA代码控制</span>        lqw<span class="token punctuation">.</span><span class="token function">like</span><span class="token punctuation">(</span>name <span class="token operator">!=</span> null<span class="token punctuation">,</span>Book<span class="token operator">:</span><span class="token operator">:</span>getName<span class="token punctuation">,</span>name<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//方式二：API接口提供控制开关</span>        bookDao<span class="token punctuation">.</span><span class="token function">selectList</span><span class="token punctuation">(</span>lqw<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>总结</strong></p><ol><li><p>使用QueryWrapper对象封装查询条件</p></li><li><p><strong>推荐</strong>使用LambdaQueryWrapper对象</p></li><li><p>所有查询操作封装成方法调用</p></li><li><p>查询条件支持动态条件拼装</p></li></ol><h2 id="4-业务层开发"><a href="#4-业务层开发" class="headerlink" title="4.业务层开发"></a>4.业务层开发</h2><h3 id="①简介"><a href="#①简介" class="headerlink" title="①简介"></a>①简介</h3><p>初学者认为业务层就是调用数据层，更精准的说法应该是<font color="#ff0000"><b>组织业务逻辑功能，并根据业务需求，对数据持久层发起调用</b></font>。</p><p>一个常识性的知识普及一下，业务层的方法名定义一定要与业务有关，例如登录操作</p><pre class="line-numbers language-java"><code class="language-java"><span class="token function">login</span><span class="token punctuation">(</span>String username<span class="token punctuation">,</span>String password<span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>而数据层的方法名定义一定与业务无关，是一定，不是可能，也不是有可能，例如根据用户名密码查询</p><pre class="line-numbers language-java"><code class="language-java"><span class="token function">selectByUserNameAndPassword</span><span class="token punctuation">(</span>String username<span class="token punctuation">,</span>String password<span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>我们在开发的时候是可以根据完成的工作不同划分成不同职能的开发团队的。</p><p>比如开发数据层的团队，拿到的需求文档要求可能是这样的</p><pre><code>接口：传入用户名与密码字段，查询出对应结果，结果是单条数据接口：传入ID字段，查询出对应结果，结果是单条数据接口：传入离职字段，查询出对应结果，结果是多条数据</code></pre><p>但是开发业务层功能团队，拿到的需求文档要求差别就很大</p><pre><code>接口：传入用户名与密码字段，对用户名字段做长度校验，4-15位，对密码字段做长度校验，8到24位，对喵喵喵字段做特殊字符校验，不允许存在空格，查询结果为对象。如果为null，返回BusinessException，封装消息码INFO_LOGON_USERNAME_PASSWORD_ERROR</code></pre><p>所以说业务层方法定义与数据层方法定义差异化很大</p><h3 id="②普通开发"><a href="#②普通开发" class="headerlink" title="②普通开发"></a>②普通开发</h3><p><strong>步骤①</strong>：业务层接口定义如下：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">BookService</span> <span class="token punctuation">{</span>    Boolean <span class="token function">save</span><span class="token punctuation">(</span>Book book<span class="token punctuation">)</span><span class="token punctuation">;</span>    Boolean <span class="token function">update</span><span class="token punctuation">(</span>Book book<span class="token punctuation">)</span><span class="token punctuation">;</span>    Boolean <span class="token function">delete</span><span class="token punctuation">(</span>Integer id<span class="token punctuation">)</span><span class="token punctuation">;</span>    Book <span class="token function">getById</span><span class="token punctuation">(</span>Integer id<span class="token punctuation">)</span><span class="token punctuation">;</span>    List<span class="token operator">&lt;</span>Book<span class="token operator">></span> <span class="token function">getAll</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    IPage<span class="token operator">&lt;</span>Book<span class="token operator">></span> <span class="token function">getPage</span><span class="token punctuation">(</span><span class="token keyword">int</span> currentPage<span class="token punctuation">,</span><span class="token keyword">int</span> pageSize<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤②</strong>：业务层实现类如下，转调数据层即可</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Service</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">BookServiceImpl</span> <span class="token keyword">implements</span> <span class="token class-name">BookService</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Autowired</span>    <span class="token keyword">private</span> BookDao bookDao<span class="token punctuation">;</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> Boolean <span class="token function">save</span><span class="token punctuation">(</span>Book book<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">return</span> bookDao<span class="token punctuation">.</span><span class="token function">insert</span><span class="token punctuation">(</span>book<span class="token punctuation">)</span> <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> Boolean <span class="token function">update</span><span class="token punctuation">(</span>Book book<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">return</span> bookDao<span class="token punctuation">.</span><span class="token function">updateById</span><span class="token punctuation">(</span>book<span class="token punctuation">)</span> <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> Boolean <span class="token function">delete</span><span class="token punctuation">(</span>Integer id<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">return</span> bookDao<span class="token punctuation">.</span><span class="token function">deleteById</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span> <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> Book <span class="token function">getById</span><span class="token punctuation">(</span>Integer id<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">return</span> bookDao<span class="token punctuation">.</span><span class="token function">selectById</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> List<span class="token operator">&lt;</span>Book<span class="token operator">></span> <span class="token function">getAll</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">return</span> bookDao<span class="token punctuation">.</span><span class="token function">selectList</span><span class="token punctuation">(</span>null<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> IPage<span class="token operator">&lt;</span>Book<span class="token operator">></span> <span class="token function">getPage</span><span class="token punctuation">(</span><span class="token keyword">int</span> currentPage<span class="token punctuation">,</span> <span class="token keyword">int</span> pageSize<span class="token punctuation">)</span> <span class="token punctuation">{</span>        IPage page <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Page</span><span class="token punctuation">(</span>currentPage<span class="token punctuation">,</span>pageSize<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> bookDao<span class="token punctuation">.</span><span class="token function">selectPage</span><span class="token punctuation">(</span>page<span class="token punctuation">,</span>null<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>​<strong>步骤③</strong>：对业务层接口进行测试</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@SpringBootTest</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">BookServiceTests</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Autowired</span>    <span class="token keyword">private</span> BookService bookService<span class="token punctuation">;</span>    <span class="token annotation punctuation">@Test</span>    <span class="token keyword">void</span> <span class="token function">testGetById</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>bookService<span class="token punctuation">.</span><span class="token function">getById</span><span class="token punctuation">(</span><span class="token number">4</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@Test</span>    <span class="token keyword">void</span> <span class="token function">testSave</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>        Book book <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Book</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        book<span class="token punctuation">.</span><span class="token function">setType</span><span class="token punctuation">(</span><span class="token string">"测试数据123"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        book<span class="token punctuation">.</span><span class="token function">setName</span><span class="token punctuation">(</span><span class="token string">"测试数据123"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        book<span class="token punctuation">.</span><span class="token function">setDescription</span><span class="token punctuation">(</span><span class="token string">"测试数据123"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        bookService<span class="token punctuation">.</span><span class="token function">save</span><span class="token punctuation">(</span>book<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@Test</span>    <span class="token keyword">void</span> <span class="token function">testUpdate</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>        Book book <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Book</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        book<span class="token punctuation">.</span><span class="token function">setId</span><span class="token punctuation">(</span><span class="token number">17</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        book<span class="token punctuation">.</span><span class="token function">setType</span><span class="token punctuation">(</span><span class="token string">"-----------------"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        book<span class="token punctuation">.</span><span class="token function">setName</span><span class="token punctuation">(</span><span class="token string">"测试数据123"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        book<span class="token punctuation">.</span><span class="token function">setDescription</span><span class="token punctuation">(</span><span class="token string">"测试数据123"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        bookService<span class="token punctuation">.</span><span class="token function">update</span><span class="token punctuation">(</span>book<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@Test</span>    <span class="token keyword">void</span> <span class="token function">testDelete</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>        bookService<span class="token punctuation">.</span><span class="token function">delete</span><span class="token punctuation">(</span><span class="token number">18</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@Test</span>    <span class="token keyword">void</span> <span class="token function">testGetAll</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>        bookService<span class="token punctuation">.</span><span class="token function">getAll</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@Test</span>    <span class="token keyword">void</span> <span class="token function">testGetPage</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>        IPage<span class="token operator">&lt;</span>Book<span class="token operator">></span> page <span class="token operator">=</span> bookService<span class="token punctuation">.</span><span class="token function">getPage</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>page<span class="token punctuation">.</span><span class="token function">getCurrent</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>page<span class="token punctuation">.</span><span class="token function">getSize</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>page<span class="token punctuation">.</span><span class="token function">getTotal</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>page<span class="token punctuation">.</span><span class="token function">getPages</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>page<span class="token punctuation">.</span><span class="token function">getRecords</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>总结</strong></p><ol><li>Service接口名称定义成业务名称，并与Dao接口名称进行区分</li><li>制作测试类测试Service功能是否有效</li></ol><h3 id="③快速开发"><a href="#③快速开发" class="headerlink" title="③快速开发"></a>③快速开发</h3><p>MP不仅提供了数据层快速开发方案，MP也给了业务层一个通用接口，<strong>不推荐使用</strong>，其实就是一个封装+继承的思想，实际开发慎用</p><p><strong>步骤①</strong>：业务层接口快速开发</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">IBookService</span> <span class="token keyword">extends</span> <span class="token class-name">IService</span><span class="token operator">&lt;</span>Book<span class="token operator">></span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">//添加非通用操作API接口</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p><strong>步骤②</strong>：业务层接口实现类快速开发，继承的类需要传入两个泛型，一个是数据层接口，另一个是实体类</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Service</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">BookServiceImpl</span> <span class="token keyword">extends</span> <span class="token class-name">ServiceImpl</span><span class="token operator">&lt;</span>BookDao<span class="token punctuation">,</span> Book<span class="token operator">></span> <span class="token keyword">implements</span> <span class="token class-name">IBookService</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Autowired</span>    <span class="token keyword">private</span> BookDao bookDao<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">//添加非通用操作API</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>如果感觉MP提供的功能不足以支撑你的使用需要，可以在原始接口基础上接着定义新的API接口就行了</p><p><strong>总结</strong></p><ol><li>使用通用接口（ISerivce&lt;T&gt;）快速开发Service</li><li>使用通用实现类（ServiceImpl&lt;M,T&gt;）快速开发ServiceImpl</li><li>可以在通用接口基础上做功能重载或功能追加</li><li>注意重载时不要覆盖原始操作，避免原始提供的功能丢失</li></ol><h2 id="5-控制层开发"><a href="#5-控制层开发" class="headerlink" title="5.控制层开发"></a>5.控制层开发</h2><h3 id="①普通Restful开发"><a href="#①普通Restful开发" class="headerlink" title="①普通Restful开发"></a>①普通Restful开发</h3><p><strong>步骤①</strong>：控制层接口如下，基于Restful开发</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@RestController</span><span class="token annotation punctuation">@RequestMapping</span><span class="token punctuation">(</span><span class="token string">"/books"</span><span class="token punctuation">)</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">BookController</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Autowired</span>    <span class="token keyword">private</span> BookService bookService<span class="token punctuation">;</span>    <span class="token annotation punctuation">@GetMapping</span>    <span class="token keyword">public</span> List<span class="token operator">&lt;</span>Book<span class="token operator">></span> <span class="token function">getAll</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>        <span class="token keyword">return</span> bookService<span class="token punctuation">.</span><span class="token function">list</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@PostMapping</span>    <span class="token keyword">public</span> Boolean <span class="token function">save</span><span class="token punctuation">(</span><span class="token annotation punctuation">@RequestBody</span> Book book<span class="token punctuation">)</span><span class="token punctuation">{</span>        <span class="token keyword">return</span> bookService<span class="token punctuation">.</span><span class="token function">save</span><span class="token punctuation">(</span>book<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@PutMapping</span>    <span class="token keyword">public</span> Boolean <span class="token function">update</span><span class="token punctuation">(</span><span class="token annotation punctuation">@RequestBody</span> Book book<span class="token punctuation">)</span><span class="token punctuation">{</span>        <span class="token keyword">return</span> bookService<span class="token punctuation">.</span><span class="token function">modify</span><span class="token punctuation">(</span>book<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@DeleteMapping</span><span class="token punctuation">(</span><span class="token string">"{id}"</span><span class="token punctuation">)</span>    <span class="token keyword">public</span> Boolean <span class="token function">delete</span><span class="token punctuation">(</span><span class="token annotation punctuation">@PathVariable</span> Integer id<span class="token punctuation">)</span><span class="token punctuation">{</span>        <span class="token keyword">return</span> bookService<span class="token punctuation">.</span><span class="token function">delete</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@GetMapping</span><span class="token punctuation">(</span><span class="token string">"{id}"</span><span class="token punctuation">)</span>    <span class="token keyword">public</span> Book <span class="token function">getById</span><span class="token punctuation">(</span><span class="token annotation punctuation">@PathVariable</span> Integer id<span class="token punctuation">)</span><span class="token punctuation">{</span>        <span class="token keyword">return</span> bookService<span class="token punctuation">.</span><span class="token function">getById</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@GetMapping</span><span class="token punctuation">(</span><span class="token string">"{currentPage}/{pageSize}"</span><span class="token punctuation">)</span>    <span class="token keyword">public</span> IPage<span class="token operator">&lt;</span>Book<span class="token operator">></span> <span class="token function">getPage</span><span class="token punctuation">(</span><span class="token annotation punctuation">@PathVariable</span> <span class="token keyword">int</span> currentPage<span class="token punctuation">,</span><span class="token annotation punctuation">@PathVariable</span> <span class="token keyword">int</span> pageSize<span class="token punctuation">)</span><span class="token punctuation">{</span>        <span class="token keyword">return</span> bookService<span class="token punctuation">.</span><span class="token function">getPage</span><span class="token punctuation">(</span>currentPage<span class="token punctuation">,</span>pageSize<span class="token punctuation">,</span> null<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤②</strong>：Postman测试，关注提交类型，对应上即可，不然就会报405的错误码了</p><ul><li><strong>普通GET请求</strong></li></ul><p><img src="https://img.jwt1399.top/img/202209100004375.png"></p><ul><li><strong>GET请求传递路径变量，后台使用@PathVariable接收数据</strong></li></ul><p><img src="https://img.jwt1399.top/img/202209100004293.png"></p><ul><li><strong>PUT请求传递json数据，后台使用@RequestBody接收数据</strong></li></ul><p><img src="https://img.jwt1399.top/img/202209100014797.png"></p><p><strong>总结</strong></p><ol><li>基于Restful制作表现层接口<ul><li>新增：@PostMapping</li><li>删除：@DeleteMapping</li><li>修改：@PutMapping</li><li>查询：@GetMapping</li></ul></li><li>接收参数<ul><li>实体数据：@RequestBody</li><li>路径变量：@PathVariable</li></ul></li></ol><h3 id="②消息一致性开发"><a href="#②消息一致性开发" class="headerlink" title="②消息一致性开发"></a>②消息一致性开发</h3><blockquote><p>为何要返回统一格式。为方便使用已封装好，导入项目即可使用：<a href="https://jwt1399.lanzouv.com/iINu80bfh1sd">https://jwt1399.lanzouv.com/iINu80bfh1sd</a></p></blockquote><p>现在大多数web项目基本都是前后端分离模式，这种模式会涉及到一个前后端对接问题，所以一套完善且规范的接口是非常有必要的，不仅能够提高对接效率，也可以让我的代码看起来更加简洁优雅。本节将解决如何返回统一的标准格式以及处理全局异常。</p><p>如果SpringBoot不使用统一返回格式，默认会有如下三种返回情况。</p><ul><li>返回字符串</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@GetMapping</span><span class="token punctuation">(</span><span class="token string">"/getUserName"</span><span class="token punctuation">)</span><span class="token keyword">public</span> String <span class="token function">getUserName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>    <span class="token keyword">return</span> <span class="token string">"Hello"</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">// 返回结果：Hello</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>返回实体类对象</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@GetMapping</span><span class="token punctuation">(</span><span class="token string">"/getUserName"</span><span class="token punctuation">)</span><span class="token keyword">public</span> User <span class="token function">getUserName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>    <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">User</span><span class="token punctuation">(</span><span class="token string">"Hello"</span><span class="token punctuation">,</span><span class="token number">18</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">// 返回结果：</span><span class="token punctuation">{</span>  <span class="token string">"name"</span><span class="token operator">:</span> <span class="token string">"Hello"</span><span class="token punctuation">,</span>  <span class="token string">"age"</span><span class="token operator">:</span> <span class="token string">"18"</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>返回异常</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@GetMapping</span><span class="token punctuation">(</span><span class="token string">"/getUserName"</span><span class="token punctuation">)</span><span class="token keyword">public</span> <span class="token keyword">static</span> String <span class="token function">getUserName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>    HashMap hashMap <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">HashMap</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> hashMap<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 模拟一个空指针异常</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">// 返回结果：</span><span class="token punctuation">{</span>    <span class="token string">"timestamp"</span><span class="token operator">:</span> <span class="token string">"2022-09-09T12:56:06.549+00:00"</span><span class="token punctuation">,</span>    <span class="token string">"status"</span><span class="token operator">:</span> <span class="token number">500</span><span class="token punctuation">,</span>    <span class="token string">"error"</span><span class="token operator">:</span> <span class="token string">"Internal Server Error"</span><span class="token punctuation">,</span>    <span class="token string">"path"</span><span class="token operator">:</span> <span class="token string">"/getUserName"</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>对于上面这几种情况，如果整个项目没有定义统一的返回格式，不同开发人员可能会定义不同的返回格式，这样会使前后端对接出现一些问题。</p><h4 id="a-定义返回标准"><a href="#a-定义返回标准" class="headerlink" title="a. 定义返回标准"></a>a. 定义返回标准</h4><p>一个标准的返回格式至少包含3部分：</p><pre><code>code： 状态码message： 接口调用的提示信息  data： 返回数据</code></pre><p><strong>步骤①</strong>：定义数据返回格式</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Data</span><span class="token annotation punctuation">@Data</span><span class="token annotation punctuation">@NoArgsConstructor</span><span class="token annotation punctuation">@AllArgsConstructor</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Result</span><span class="token operator">&lt;</span>T<span class="token operator">></span> <span class="token punctuation">{</span>    <span class="token keyword">private</span> <span class="token keyword">int</span> code<span class="token punctuation">;</span>    <span class="token keyword">private</span> String message<span class="token punctuation">;</span>    <span class="token keyword">private</span> T data<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">/**     * 成功     */</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token operator">&lt;</span>T<span class="token operator">></span> Result<span class="token operator">&lt;</span>T<span class="token operator">></span> <span class="token function">success</span><span class="token punctuation">(</span>T data<span class="token punctuation">)</span> <span class="token punctuation">{</span>        Result<span class="token operator">&lt;</span>T<span class="token operator">></span> result <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Result</span><span class="token operator">&lt;</span>T<span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        result<span class="token punctuation">.</span><span class="token function">setCode</span><span class="token punctuation">(</span>ResultMsgEnum<span class="token punctuation">.</span>SUCCESS<span class="token punctuation">.</span><span class="token function">getCode</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        result<span class="token punctuation">.</span><span class="token function">setMessage</span><span class="token punctuation">(</span>ResultMsgEnum<span class="token punctuation">.</span>SUCCESS<span class="token punctuation">.</span><span class="token function">getMessage</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        result<span class="token punctuation">.</span><span class="token function">setData</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> result<span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">/**     * 失败     */</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token operator">&lt;</span>T<span class="token operator">></span> Result<span class="token operator">&lt;</span>T<span class="token operator">></span> <span class="token function">fail</span><span class="token punctuation">(</span>ResultMsgEnum resultMsgEnum<span class="token punctuation">)</span> <span class="token punctuation">{</span>        Result<span class="token operator">&lt;</span>T<span class="token operator">></span> result <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Result</span><span class="token operator">&lt;</span>T<span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        result<span class="token punctuation">.</span><span class="token function">setCode</span><span class="token punctuation">(</span>resultMsgEnum<span class="token punctuation">.</span><span class="token function">getCode</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        result<span class="token punctuation">.</span><span class="token function">setMessage</span><span class="token punctuation">(</span>resultMsgEnum<span class="token punctuation">.</span><span class="token function">getMessage</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> result<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤②</strong>：定义状态码</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Getter</span><span class="token annotation punctuation">@NoArgsConstructor</span><span class="token annotation punctuation">@AllArgsConstructor</span><span class="token keyword">public</span> <span class="token keyword">enum</span> ResultMsgEnum <span class="token punctuation">{</span>    <span class="token function">SUCCESS</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token string">"成功"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>    <span class="token function">FAIL</span><span class="token punctuation">(</span><span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token string">"失败"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>    <span class="token function">TEST_ERROR</span><span class="token punctuation">(</span><span class="token number">400</span><span class="token punctuation">,</span> <span class="token string">"发生错误啦!"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">private</span> <span class="token keyword">int</span> code<span class="token punctuation">;</span>    <span class="token keyword">private</span> String message<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤③</strong>：使用</p><p>上面定义了数据返回格式和状态码，下面在接口中使用。</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">//示例1</span><span class="token annotation punctuation">@GetMapping</span><span class="token punctuation">(</span><span class="token string">"/getUserName"</span><span class="token punctuation">)</span><span class="token keyword">public</span> Result <span class="token function">getUserName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>    <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">success</span><span class="token punctuation">(</span><span class="token string">"Hello"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">//示例2</span><span class="token annotation punctuation">@GetMapping</span><span class="token punctuation">(</span><span class="token string">"/getUserName"</span><span class="token punctuation">)</span><span class="token keyword">public</span> <span class="token keyword">static</span> Result <span class="token function">getUserName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>  HashMap hashMap <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">HashMap</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">success</span><span class="token punctuation">(</span>hashMap<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 模拟一个空指针异常</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>返回结果如下</p><pre class="line-numbers language-powershell"><code class="language-powershell"><span class="token comment" spellcheck="true"># 示例1结果</span><span class="token punctuation">{</span>    <span class="token string">"code"</span>: 0<span class="token punctuation">,</span>    <span class="token string">"message"</span>: <span class="token string">"成功"</span><span class="token punctuation">,</span>    <span class="token string">"data"</span>: <span class="token string">"Hello"</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true"># 示例2结果</span><span class="token punctuation">{</span>    <span class="token string">"timestamp"</span>: <span class="token string">"2022-09-09T14:45:21.875+00:00"</span><span class="token punctuation">,</span>    <span class="token string">"status"</span>: 500<span class="token punctuation">,</span>    <span class="token string">"error"</span>: <span class="token string">"Internal Server Error"</span><span class="token punctuation">,</span>    <span class="token string">"path"</span>: <span class="token string">"/getUserName"</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>通过在Controller层调用Result.success()对返回结果进行包装后返回给前端，虽然能够满足日常需求，但是当有大量的接口时，每一个接口中都使用Result.success()来包装返回信息就会增加很多重复代码，而且遇到异常数据格式无法统一。</p><h4 id="c-统一接口返回"><a href="#c-统一接口返回" class="headerlink" title="c .统一接口返回"></a>c .统一接口返回</h4><p>前面步骤不够优雅，可以继续改进，用 <strong>@RestControllerAdvice</strong> 注解，拦截下后端返回的数据，实现 <strong>ResponseBodyAdvice</strong> 接口对数据做一层包装再返回给前端。</p><blockquote><p><code>ResponseBodyAdvice</code>： 该接口是SpringMVC 4.1提供的，它允许在 执行@ResponseBody后自定义返回数据，用来封装统一数据格式返回；拦截Controller方法的返回值，统一处理返回值&#x2F;响应体，一般用来统一返回格式，加解密，签名等</p><p><code>@RestControllerAdvice</code>： 该注解是Controller的增强版，可以全局捕获抛出的异常，全局数据绑定，全局数据预处理。</p></blockquote><p><strong>步骤①</strong>：新建ResponseAdvice类，该类用于统一封装controller中接口的返回结果。实现ResponseBodyAdvice接口，实现supports、beforeBodyWrite方法。</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@RestControllerAdvice</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ResponseAdvice</span> <span class="token keyword">implements</span> <span class="token class-name">ResponseBodyAdvice</span><span class="token operator">&lt;</span>Object<span class="token operator">></span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Autowired</span>    <span class="token keyword">private</span> ObjectMapper objectMapper<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">/**     * 是否开启功能 true:开启     */</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> <span class="token keyword">boolean</span> <span class="token function">supports</span><span class="token punctuation">(</span>MethodParameter methodParameter<span class="token punctuation">,</span> Class<span class="token operator">&lt;</span><span class="token operator">?</span> <span class="token keyword">extends</span> <span class="token class-name">HttpMessageConverter</span><span class="token operator">&lt;</span><span class="token operator">?</span><span class="token operator">>></span> aClass<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">/**     * 处理返回结果     */</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> Object <span class="token function">beforeBodyWrite</span><span class="token punctuation">(</span>Object o<span class="token punctuation">,</span> MethodParameter methodParameter<span class="token punctuation">,</span> MediaType mediaType<span class="token punctuation">,</span> Class<span class="token operator">&lt;</span><span class="token operator">?</span> <span class="token keyword">extends</span> <span class="token class-name">HttpMessageConverter</span><span class="token operator">&lt;</span><span class="token operator">?</span><span class="token operator">>></span> aClass<span class="token punctuation">,</span> ServerHttpRequest serverHttpRequest<span class="token punctuation">,</span> ServerHttpResponse serverHttpResponse<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">//处理字符串类型数据</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>o <span class="token keyword">instanceof</span> <span class="token class-name">String</span><span class="token punctuation">)</span><span class="token punctuation">{</span>            <span class="token keyword">try</span> <span class="token punctuation">{</span>                <span class="token keyword">return</span> objectMapper<span class="token punctuation">.</span><span class="token function">writeValueAsString</span><span class="token punctuation">(</span>Result<span class="token punctuation">.</span><span class="token function">success</span><span class="token punctuation">(</span>o<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">JsonProcessingException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span>                e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">//返回类型是否已经封装</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>o <span class="token keyword">instanceof</span> <span class="token class-name">Result</span><span class="token punctuation">)</span><span class="token punctuation">{</span>            <span class="token keyword">return</span> o<span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">success</span><span class="token punctuation">(</span>o<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤②</strong>：测试，无需转换格式，直接使用</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@GetMapping</span><span class="token punctuation">(</span><span class="token string">"/getUserName"</span><span class="token punctuation">)</span><span class="token keyword">public</span> <span class="token keyword">static</span> String <span class="token function">getUserName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>  HashMap hashMap <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">HashMap</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">return</span> hashMap<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 模拟一个空指针异常</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>返回结果如下，可以看到返回结果与在Result中定义的参数类型相同。</p><pre class="line-numbers language-powershell"><code class="language-powershell"><span class="token punctuation">{</span>    <span class="token string">"code"</span>: 0<span class="token punctuation">,</span>    <span class="token string">"message"</span>: <span class="token string">"成功"</span><span class="token punctuation">,</span>    <span class="token string">"data"</span>: <span class="token punctuation">{</span>        <span class="token string">"timestamp"</span>: <span class="token string">"2022-09-09T14:37:19.902+00:00"</span><span class="token punctuation">,</span>        <span class="token string">"status"</span>: 500<span class="token punctuation">,</span>        <span class="token string">"error"</span>: <span class="token string">"Internal Server Error"</span><span class="token punctuation">,</span>        <span class="token string">"path"</span>: <span class="token string">"/getUserName"</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="d-全局异常处理"><a href="#d-全局异常处理" class="headerlink" title="d.全局异常处理"></a>d.全局异常处理</h4><blockquote><p>遇到异常时，第一时间想到的应该是try…catch，不过这种方式会导致大量代码重复，维护困难等问题，这里不用手写try…catch，由全局异常处理器统一捕获；对于自定义异常，只能通过全局异常处理器来处理，使用全局异常处理器最大的便利就是程序员在写代码时不再需要手写 try…catch了。  </p></blockquote><p><strong>步骤①</strong>：首先新增一个类，增加<code>@RestControllerAdvice</code>注解，如果我们有想要拦截的异常类型，就新增一个方法，使用<code>@ExceptionHandler</code>注解修饰，注解参数为目标异常类型。</p><p>例如：controller中接口发生Exception异常时，就会进入到Execption方法中进行捕获，将杂乱的异常信息，转换成指定格式后交给ResponseAdvice方法进行统一格式封装并返回给前端。</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@RestControllerAdvice</span><span class="token annotation punctuation">@Slf4j</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ExceptionAdvice</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@ExceptionHandler</span><span class="token punctuation">(</span>Exception<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span>    <span class="token keyword">public</span> Result <span class="token function">Execption</span><span class="token punctuation">(</span>Exception e<span class="token punctuation">)</span> <span class="token punctuation">{</span>        log<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token string">"未知异常！"</span><span class="token punctuation">,</span> e<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">//e.printStackTrace();</span>        <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">fail</span><span class="token punctuation">(</span>ResultMsgEnum<span class="token punctuation">.</span>TEST_ERROR<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">//还可定义其它拦截异常方法</span>    <span class="token comment" spellcheck="true">//。。。。。。</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤②</strong>：测试，再次调用接口getUserName查看返回结果</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@GetMapping</span><span class="token punctuation">(</span><span class="token string">"/getUserName"</span><span class="token punctuation">)</span><span class="token keyword">public</span> <span class="token keyword">static</span> String <span class="token function">getUserName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>    HashMap hashMap <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">HashMap</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> hashMap<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 模拟一个空指针异常</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>返回结果</p><pre class="line-numbers language-powershell"><code class="language-powershell"><span class="token punctuation">{</span>    <span class="token string">"code"</span>: 400<span class="token punctuation">,</span>    <span class="token string">"message"</span>: <span class="token string">"发生错误啦!"</span><span class="token punctuation">,</span>    <span class="token string">"data"</span>: null<span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="e-快速使用"><a href="#e-快速使用" class="headerlink" title="e.快速使用"></a>e.快速使用</h4><blockquote><p>前面实现了消息一致性，为了方便使用作如下封装，只需引用到项目中即可</p></blockquote><p><strong>步骤①</strong>：定义数据返回格式</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Data</span><span class="token annotation punctuation">@Data</span><span class="token annotation punctuation">@NoArgsConstructor</span><span class="token annotation punctuation">@AllArgsConstructor</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Result</span><span class="token operator">&lt;</span>T<span class="token operator">></span> <span class="token punctuation">{</span>    <span class="token keyword">private</span> <span class="token keyword">int</span> code<span class="token punctuation">;</span>    <span class="token keyword">private</span> String message<span class="token punctuation">;</span>    <span class="token keyword">private</span> T data<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">/**     * 成功     */</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token operator">&lt;</span>T<span class="token operator">></span> Result<span class="token operator">&lt;</span>T<span class="token operator">></span> <span class="token function">success</span><span class="token punctuation">(</span>T data<span class="token punctuation">)</span> <span class="token punctuation">{</span>        Result<span class="token operator">&lt;</span>T<span class="token operator">></span> result <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Result</span><span class="token operator">&lt;</span>T<span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        result<span class="token punctuation">.</span><span class="token function">setCode</span><span class="token punctuation">(</span>ResultMsgEnum<span class="token punctuation">.</span>SUCCESS<span class="token punctuation">.</span><span class="token function">getCode</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        result<span class="token punctuation">.</span><span class="token function">setMessage</span><span class="token punctuation">(</span>ResultMsgEnum<span class="token punctuation">.</span>SUCCESS<span class="token punctuation">.</span><span class="token function">getMessage</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        result<span class="token punctuation">.</span><span class="token function">setData</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> result<span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">/**     * 失败     */</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token operator">&lt;</span>T<span class="token operator">></span> Result<span class="token operator">&lt;</span>T<span class="token operator">></span> <span class="token function">fail</span><span class="token punctuation">(</span>ResultMsgEnum resultMsgEnum<span class="token punctuation">)</span> <span class="token punctuation">{</span>        Result<span class="token operator">&lt;</span>T<span class="token operator">></span> result <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Result</span><span class="token operator">&lt;</span>T<span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        result<span class="token punctuation">.</span><span class="token function">setCode</span><span class="token punctuation">(</span>resultMsgEnum<span class="token punctuation">.</span><span class="token function">getCode</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        result<span class="token punctuation">.</span><span class="token function">setMessage</span><span class="token punctuation">(</span>resultMsgEnum<span class="token punctuation">.</span><span class="token function">getMessage</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> result<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤②</strong>：定义状态码</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Getter</span><span class="token annotation punctuation">@NoArgsConstructor</span><span class="token annotation punctuation">@AllArgsConstructor</span><span class="token keyword">public</span> <span class="token keyword">enum</span> ResultMsgEnum <span class="token punctuation">{</span>    <span class="token function">SUCCESS</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token string">"成功"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>    <span class="token function">FAIL</span><span class="token punctuation">(</span><span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token string">"失败"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>    <span class="token function">TEST_ERROR</span><span class="token punctuation">(</span><span class="token number">400</span><span class="token punctuation">,</span> <span class="token string">"发生错误啦!"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">private</span> <span class="token keyword">int</span> code<span class="token punctuation">;</span>    <span class="token keyword">private</span> String message<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤③：</strong>统一接口</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@RestControllerAdvice</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ResponseAdvice</span> <span class="token keyword">implements</span> <span class="token class-name">ResponseBodyAdvice</span><span class="token operator">&lt;</span>Object<span class="token operator">></span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Autowired</span>    <span class="token keyword">private</span> ObjectMapper objectMapper<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">/**     * 是否开启功能 true:开启     */</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> <span class="token keyword">boolean</span> <span class="token function">supports</span><span class="token punctuation">(</span>MethodParameter methodParameter<span class="token punctuation">,</span> Class<span class="token operator">&lt;</span><span class="token operator">?</span> <span class="token keyword">extends</span> <span class="token class-name">HttpMessageConverter</span><span class="token operator">&lt;</span><span class="token operator">?</span><span class="token operator">>></span> aClass<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">/**     * 处理返回结果     */</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> Object <span class="token function">beforeBodyWrite</span><span class="token punctuation">(</span>Object o<span class="token punctuation">,</span> MethodParameter methodParameter<span class="token punctuation">,</span> MediaType mediaType<span class="token punctuation">,</span> Class<span class="token operator">&lt;</span><span class="token operator">?</span> <span class="token keyword">extends</span> <span class="token class-name">HttpMessageConverter</span><span class="token operator">&lt;</span><span class="token operator">?</span><span class="token operator">>></span> aClass<span class="token punctuation">,</span> ServerHttpRequest serverHttpRequest<span class="token punctuation">,</span> ServerHttpResponse serverHttpResponse<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">//处理字符串类型数据</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>o <span class="token keyword">instanceof</span> <span class="token class-name">String</span><span class="token punctuation">)</span><span class="token punctuation">{</span>            <span class="token keyword">try</span> <span class="token punctuation">{</span>                <span class="token keyword">return</span> objectMapper<span class="token punctuation">.</span><span class="token function">writeValueAsString</span><span class="token punctuation">(</span>Result<span class="token punctuation">.</span><span class="token function">success</span><span class="token punctuation">(</span>o<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">JsonProcessingException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span>                e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">//返回类型是否已经封装</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>o <span class="token keyword">instanceof</span> <span class="token class-name">Result</span><span class="token punctuation">)</span><span class="token punctuation">{</span>            <span class="token keyword">return</span> o<span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">success</span><span class="token punctuation">(</span>o<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤④</strong>：全局异常</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@RestControllerAdvice</span><span class="token annotation punctuation">@Slf4j</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ExceptionAdvice</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@ExceptionHandler</span><span class="token punctuation">(</span>Exception<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span>    <span class="token keyword">public</span> Result <span class="token function">Execption</span><span class="token punctuation">(</span>Exception e<span class="token punctuation">)</span> <span class="token punctuation">{</span>        log<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token string">"未知异常！"</span><span class="token punctuation">,</span> e<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">//e.printStackTrace();</span>        <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">fail</span><span class="token punctuation">(</span>ResultMsgEnum<span class="token punctuation">.</span>TEST_ERROR<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">//还可定义其它异常方法</span>    <span class="token comment" spellcheck="true">//。。。。。。</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>总结</strong></p><ol><li><p>设计统一的返回值结果类型便于前端开发读取数据</p></li><li><p>返回值结果类型可以根据需求自行设定，没有固定格式</p></li><li><p>返回值结果模型类用于后端与前端进行数据格式统一，也称为前后端数据协议</p></li></ol><p><strong>参考</strong></p><ul><li><p><a href="https://blog.csdn.net/qq_47183158/article/details/123440041">SpringBoot统一接口返回和全局异常处理</a></p></li><li><p><a href="https://juejin.cn/post/7032609987083894814#heading-6">统一接口返回和全局异常处理 </a></p></li></ul><h2 id="6-页面功能开发"><a href="#6-页面功能开发" class="headerlink" title="6.页面功能开发"></a>6.页面功能开发</h2><h3 id="⓪前端页面"><a href="#⓪前端页面" class="headerlink" title="⓪前端页面"></a>⓪前端页面</h3><p>前端页面下载：<a href="https://jwt1399.lanzouv.com/ibQNG0bet3yh">https://jwt1399.lanzouv.com/ibQNG0bet3yh</a></p><p>将页面保存到 resources&#x2F;static目录中，建议执行maven的clean生命周期，避免缓存的问题出现。</p><p>如果成功访问：<a href="http://127.0.0.1/pages/books.html%EF%BC%8C%E5%8D%B3%E8%A1%A8%E6%98%8E%E6%8E%A5%E5%85%A5%E6%88%90%E5%8A%9F">http://127.0.0.1/pages/books.html，即表明接入成功</a></p><p>在进行具体的功能开发之前，先做前后端联通性的测试，通过页面发送异步提交（axios）</p><pre class="line-numbers language-js"><code class="language-js"><span class="token comment" spellcheck="true">//列表</span><span class="token function">getAll</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    axios<span class="token punctuation">.</span><span class="token keyword">get</span><span class="token punctuation">(</span><span class="token string">"/books"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span>res<span class="token punctuation">)</span><span class="token operator">=</span><span class="token operator">></span><span class="token punctuation">{</span>        console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>res<span class="token punctuation">.</span>data<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token punctuation">,</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-js"><code class="language-js"><span class="token comment" spellcheck="true">//钩子函数，VUE对象初始化完成后自动执行</span><span class="token function">created</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>  <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">getAll</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token punctuation">,</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>只要后台代码能够正常工作，前端能够在日志中接收到数据，就证明前后端是通的，即可进行下一步的功能开发了</p><p><strong>总结</strong></p><ol><li>单体项目中页面放置在resources&#x2F;static目录下</li><li>created钩子函数用于初始化页面时发起调用</li><li>页面使用axios发送异步请求获取数据后确认前后端是否联通</li></ol><h3 id="①列表功能"><a href="#①列表功能" class="headerlink" title="①列表功能"></a>①列表功能</h3><p>列表功能主要操作就是加载完数据，将数据展示到页面上，此处要利用VUE的数据模型绑定，发送请求得到数据，然后页面上读取指定数据即可</p><p><strong>a.页面数据模型定义</strong></p><pre class="line-numbers language-js"><code class="language-js">data<span class="token punctuation">:</span><span class="token punctuation">{</span>    dataList<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span><span class="token comment" spellcheck="true">//当前页要展示的列表数据</span>    <span class="token operator">...</span><span class="token punctuation">}</span><span class="token punctuation">,</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p><strong>b.异步请求获取数据</strong></p><pre class="line-numbers language-js"><code class="language-js"><span class="token comment" spellcheck="true">//列表</span><span class="token function">getAll</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    axios<span class="token punctuation">.</span><span class="token keyword">get</span><span class="token punctuation">(</span><span class="token string">"/books"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span>res<span class="token punctuation">)</span><span class="token operator">=</span><span class="token operator">></span><span class="token punctuation">{</span>        <span class="token keyword">this</span><span class="token punctuation">.</span>dataList <span class="token operator">=</span> res<span class="token punctuation">.</span>data<span class="token punctuation">.</span>data<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token punctuation">,</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>c.钩子函数调用请求</strong></p><pre class="line-numbers language-js"><code class="language-js"><span class="token comment" spellcheck="true">//钩子函数，VUE对象初始化完成后自动执行</span><span class="token function">created</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>  <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">getAll</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token punctuation">,</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>这样在页面加载时就可以获取到数据，并且由VUE将数据展示到页面上了</p><h3 id="②添加功能"><a href="#②添加功能" class="headerlink" title="②添加功能"></a>②添加功能</h3><p>添加功能首先要进行数据收集窗口弹窗的展示，添加后隐藏弹窗即可。因为这个弹窗一直存在，因此当页面加载时首先设置这个弹窗为不可显示状态，需要展示时切换状态即可</p><p><strong>a.表单窗口默认状态</strong></p><pre class="line-numbers language-js"><code class="language-js">data<span class="token punctuation">:</span><span class="token punctuation">{</span>    dialogFormVisible<span class="token punctuation">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span><span class="token comment" spellcheck="true">//添加表单是否可见</span>    <span class="token operator">...</span><span class="token punctuation">}</span><span class="token punctuation">,</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p><strong>b.切换为显示状态</strong></p><pre class="line-numbers language-js"><code class="language-js"><span class="token comment" spellcheck="true">//弹出添加窗口</span><span class="token function">handleCreate</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>dialogFormVisible <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token punctuation">,</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p><strong>c.定义清理数据操作</strong></p><p>由于每次添加数据都是使用同一个弹窗录入数据，所以每次操作的痕迹将在下一次操作时展示出来，需要在每次操作之前清理掉上次操作的痕迹</p><pre class="line-numbers language-js"><code class="language-js"><span class="token comment" spellcheck="true">//重置表单</span><span class="token function">resetForm</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>formData <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token punctuation">,</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p><strong>d.切换弹窗状态时清理数据</strong></p><pre class="line-numbers language-js"><code class="language-js"><span class="token comment" spellcheck="true">//弹出添加窗口</span><span class="token function">handleCreate</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>dialogFormVisible <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">resetForm</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token punctuation">,</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>e.添加操作</strong></p><ol><li>将要保存的数据传递到后台，通过post请求的第二个参数传递json数据到后台</li><li>根据返回的操作结果决定下一步操作<ul><li>如果是状态码是成功就关闭添加窗口，显示添加成功的消息</li><li>反之保留添加窗口，显示添加失败的消息</li></ul></li><li>无论添加是否成功，页面均进行刷新，动态加载数据（对getAll操作发起调用）</li></ol><pre class="line-numbers language-js"><code class="language-js"><span class="token comment" spellcheck="true">//添加</span>handleAdd <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">//发送异步请求</span>    axios<span class="token punctuation">.</span><span class="token function">post</span><span class="token punctuation">(</span><span class="token string">"/books"</span><span class="token punctuation">,</span><span class="token keyword">this</span><span class="token punctuation">.</span>formData<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span>res<span class="token punctuation">)</span><span class="token operator">=</span><span class="token operator">></span><span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">//如果操作成功，关闭弹层，显示数据</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>res<span class="token punctuation">.</span>data<span class="token punctuation">.</span>code <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">{</span>            <span class="token keyword">this</span><span class="token punctuation">.</span>dialogFormVisible <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>            <span class="token keyword">this</span><span class="token punctuation">.</span>$message<span class="token punctuation">.</span><span class="token function">success</span><span class="token punctuation">(</span><span class="token string">"添加成功"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span><span class="token keyword">else</span> <span class="token punctuation">{</span>              <span class="token keyword">this</span><span class="token punctuation">.</span>$message<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token string">"添加失败"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token keyword">finally</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">=</span><span class="token operator">></span><span class="token punctuation">{</span>        <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">getAll</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//重载数据，避免添加后数据不显示</span>    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token punctuation">,</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>f.取消添加操作</strong></p><pre class="line-numbers language-js"><code class="language-js"><span class="token comment" spellcheck="true">//取消</span><span class="token function">cancel</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>dialogFormVisible <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>$message<span class="token punctuation">.</span><span class="token function">info</span><span class="token punctuation">(</span><span class="token string">"操作取消"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token punctuation">,</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>总结</strong></p><ol><li>请求方式使用POST调用后台对应操作</li><li>添加操作结束后动态刷新页面加载数据</li><li>根据操作结果不同，显示对应的提示信息</li><li>弹出添加表单时清除上次表单数据</li></ol><h3 id="③删除功能"><a href="#③删除功能" class="headerlink" title="③删除功能"></a>③删除功能</h3><p>模仿添加操作制作删除功能，差别之处在于删除操作仅传递一个待删除的数据id到后台即可</p><p><strong>a.删除操作</strong></p><pre class="line-numbers language-js"><code class="language-js"><span class="token comment" spellcheck="true">// 删除</span><span class="token function">handleDelete</span><span class="token punctuation">(</span>row<span class="token punctuation">)</span> <span class="token punctuation">{</span>    axios<span class="token punctuation">.</span><span class="token keyword">delete</span><span class="token punctuation">(</span><span class="token string">"/books/"</span><span class="token operator">+</span>row<span class="token punctuation">.</span>id<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span>res<span class="token punctuation">)</span><span class="token operator">=</span><span class="token operator">></span><span class="token punctuation">{</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>res<span class="token punctuation">.</span>data<span class="token punctuation">.</span>flag<span class="token punctuation">)</span><span class="token punctuation">{</span>            <span class="token keyword">this</span><span class="token punctuation">.</span>$message<span class="token punctuation">.</span><span class="token function">success</span><span class="token punctuation">(</span><span class="token string">"删除成功"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span><span class="token keyword">else</span><span class="token punctuation">{</span>            <span class="token keyword">this</span><span class="token punctuation">.</span>$message<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token string">"删除失败"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token keyword">finally</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">=</span><span class="token operator">></span><span class="token punctuation">{</span>        <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">getAll</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token punctuation">,</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>b.删除操作提示信息</strong></p><pre class="line-numbers language-js"><code class="language-js"><span class="token comment" spellcheck="true">// 删除</span><span class="token function">handleDelete</span><span class="token punctuation">(</span>row<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">//1.弹出提示框</span>    <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">$confirm</span><span class="token punctuation">(</span><span class="token string">"此操作永久删除当前数据，是否继续？"</span><span class="token punctuation">,</span><span class="token string">"提示"</span><span class="token punctuation">,</span><span class="token punctuation">{</span>type<span class="token punctuation">:</span><span class="token string">'info'</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">=</span><span class="token operator">></span><span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">//2.删除业务</span>        axios<span class="token punctuation">.</span><span class="token keyword">delete</span><span class="token punctuation">(</span><span class="token string">"/books/"</span><span class="token operator">+</span>row<span class="token punctuation">.</span>id<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span>res<span class="token punctuation">)</span><span class="token operator">=</span><span class="token operator">></span><span class="token punctuation">{</span>          <span class="token keyword">if</span><span class="token punctuation">(</span>res<span class="token punctuation">.</span>data<span class="token punctuation">.</span>code <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">{</span>                <span class="token keyword">this</span><span class="token punctuation">.</span>$message<span class="token punctuation">.</span><span class="token function">success</span><span class="token punctuation">(</span><span class="token string">"删除成功"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>          <span class="token punctuation">}</span><span class="token keyword">else</span> <span class="token punctuation">{</span>                <span class="token keyword">this</span><span class="token punctuation">.</span>$message<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token string">"删除失败"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>          <span class="token punctuation">}</span>        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token keyword">finally</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">=</span><span class="token operator">></span><span class="token punctuation">{</span>             <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">getAll</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token keyword">catch</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">=</span><span class="token operator">></span><span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">//3.取消删除</span>        <span class="token keyword">this</span><span class="token punctuation">.</span>$message<span class="token punctuation">.</span><span class="token function">info</span><span class="token punctuation">(</span><span class="token string">"取消删除操作"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span>，<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>总结</strong></p><ol><li>请求方式使用Delete调用后台对应操作</li><li>删除操作需要传递当前行数据对应的id值到后台</li><li>删除操作结束后动态刷新页面加载数据</li><li>根据操作结果不同，显示对应的提示信息</li><li>删除操作前弹出提示框避免误操作</li></ol><h3 id="④修改功能"><a href="#④修改功能" class="headerlink" title="④修改功能"></a>④修改功能</h3><p>修改功能可以说是列表功能、删除功能与添加功能的合体。几个相似点如下：</p><ol><li><p>页面也需要有一个弹窗用来加载修改的数据，这一点与添加相同，都是要弹窗</p></li><li><p>弹出窗口中要加载待修改的数据，而数据需要通过查询得到，这一点与查询相同，都是要查数据</p></li><li><p>查询操作需要将要修改的数据id发送到后台，这一点与删除相同，都是传递id到后台</p></li><li><p>查询得到数据后需要展示到弹窗中，这一点与查询相同，都是要通过数据模型绑定展示数据</p></li><li><p>修改数据时需要将被修改的数据传递到后台，这一点与添加相同，都是要传递数据</p></li></ol><p><strong>a.查询并展示数据</strong></p><pre class="line-numbers language-js"><code class="language-js"><span class="token comment" spellcheck="true">//弹出编辑窗口</span><span class="token function">handleUpdate</span><span class="token punctuation">(</span>row<span class="token punctuation">)</span> <span class="token punctuation">{</span>    axios<span class="token punctuation">.</span><span class="token keyword">get</span><span class="token punctuation">(</span><span class="token string">"/books/"</span><span class="token operator">+</span>row<span class="token punctuation">.</span>id<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span>res<span class="token punctuation">)</span><span class="token operator">=</span><span class="token operator">></span><span class="token punctuation">{</span>      <span class="token keyword">if</span><span class="token punctuation">(</span>res<span class="token punctuation">.</span>data<span class="token punctuation">.</span>code <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">{</span>          <span class="token comment" spellcheck="true">//展示弹层，加载数据</span>          <span class="token keyword">this</span><span class="token punctuation">.</span>formData <span class="token operator">=</span> res<span class="token punctuation">.</span>data<span class="token punctuation">.</span>data<span class="token punctuation">;</span>          <span class="token keyword">this</span><span class="token punctuation">.</span>dialogFormVisibleEdit <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>      <span class="token punctuation">}</span><span class="token keyword">else</span> <span class="token punctuation">{</span>            <span class="token keyword">this</span><span class="token punctuation">.</span>$message<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token string">"数据同步失败"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token punctuation">}</span>    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token punctuation">,</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>b.修改操作</strong></p><pre class="line-numbers language-js"><code class="language-js"><span class="token comment" spellcheck="true">//修改</span><span class="token function">handleEdit</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    axios<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token string">"/books"</span><span class="token punctuation">,</span><span class="token keyword">this</span><span class="token punctuation">.</span>formData<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span>res<span class="token punctuation">)</span><span class="token operator">=</span><span class="token operator">></span><span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">//如果操作成功，关闭弹层并刷新页面</span>      <span class="token keyword">if</span><span class="token punctuation">(</span>res<span class="token punctuation">.</span>data<span class="token punctuation">.</span>code <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">{</span>          <span class="token keyword">this</span><span class="token punctuation">.</span>dialogFormVisibleEdit <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>          <span class="token keyword">this</span><span class="token punctuation">.</span>$message<span class="token punctuation">.</span><span class="token function">success</span><span class="token punctuation">(</span><span class="token string">"修改成功"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token punctuation">}</span><span class="token keyword">else</span> <span class="token punctuation">{</span>            <span class="token keyword">this</span><span class="token punctuation">.</span>$message<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token string">"修改失败"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token punctuation">}</span>    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token keyword">finally</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">=</span><span class="token operator">></span><span class="token punctuation">{</span>            <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">getAll</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token punctuation">,</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>c.取消修改操作</strong></p><pre class="line-numbers language-js"><code class="language-js"><span class="token comment" spellcheck="true">//取消</span><span class="token function">cancel</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>dialogFormVisible <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>      <span class="token keyword">this</span><span class="token punctuation">.</span>dialogFormVisibleEdit <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>       <span class="token keyword">this</span><span class="token punctuation">.</span>$message<span class="token punctuation">.</span><span class="token function">info</span><span class="token punctuation">(</span><span class="token string">"操作取消"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token punctuation">,</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>总结</strong></p><ol><li>加载要修改数据通过传递当前行数据对应的id值到后台查询数据</li><li>利用前端双向数据绑定将查询到的数据进行回显</li><li>请求方式使用PUT调用后台对应操作</li><li>修改操作结束后动态刷新页面加载数据</li><li>根据操作结果不同，显示对应的提示信息</li></ol><h3 id="⑤分页功能"><a href="#⑤分页功能" class="headerlink" title="⑤分页功能"></a>⑤分页功能</h3><p>分页功能的制作用于替换前面的查询全部，</p><p><strong>a.ElementUI分页组件</strong></p><pre class="line-numbers language-html"><code class="language-html"><span class="token comment" spellcheck="true">&lt;!--分页组件--></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>pagination-container<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>el-pagination</span>     <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>pagiantion<span class="token punctuation">"</span></span>     <span class="token attr-name">@current-change</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>handleCurrentChange<span class="token punctuation">"</span></span>     <span class="token attr-name">@size-change</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>handleSizeChange<span class="token punctuation">"</span></span>     <span class="token attr-name">:current-page</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>pagination.currentPage<span class="token punctuation">"</span></span>     <span class="token attr-name">:page-size</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>pagination.pageSize<span class="token punctuation">"</span></span>     <span class="token attr-name">layout</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>total,sizes, prev, pager, next, jumper<span class="token punctuation">"</span></span>     <span class="token attr-name">:page-sizes</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>[5, 10, 15, 20]<span class="token punctuation">"</span></span>     <span class="token attr-name">:total</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>pagination.total<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>el-pagination</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>b.配合分页组件，封装分页对应的数据模型</strong></p><pre class="line-numbers language-js"><code class="language-js">data<span class="token punctuation">:</span><span class="token punctuation">{</span>  <span class="token comment" spellcheck="true">//分页相关模型数据</span>    pagination<span class="token punctuation">:</span> <span class="token punctuation">{</span>        currentPage<span class="token punctuation">:</span> <span class="token number">1</span><span class="token punctuation">,</span><span class="token comment" spellcheck="true">//当前页码</span>        pageSize<span class="token punctuation">:</span><span class="token number">10</span><span class="token punctuation">,</span> <span class="token comment" spellcheck="true">//每页显示的记录数</span>        total<span class="token punctuation">:</span><span class="token number">0</span><span class="token punctuation">,</span>  <span class="token comment" spellcheck="true">//总记录数</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token punctuation">,</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>c.修改查询全部功能为分页查询，通过路径变量传递页码信息参数</strong></p><pre class="line-numbers language-js"><code class="language-js"><span class="token function">getAll</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    axios<span class="token punctuation">.</span><span class="token keyword">get</span><span class="token punctuation">(</span><span class="token string">"/books/"</span><span class="token operator">+</span><span class="token keyword">this</span><span class="token punctuation">.</span>pagination<span class="token punctuation">.</span>currentPage<span class="token operator">+</span><span class="token string">"/"</span><span class="token operator">+</span><span class="token keyword">this</span><span class="token punctuation">.</span>pagination<span class="token punctuation">.</span>pageSize<span class="token punctuation">)</span>        <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span>res<span class="token punctuation">)</span><span class="token operator">=</span><span class="token operator">></span><span class="token punctuation">{</span>            console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>res<span class="token punctuation">.</span>data<span class="token punctuation">.</span>data<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token punctuation">,</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>d.页面根据分页操作结果读取对应数据，并进行数据模型绑定</strong></p><pre class="line-numbers language-js"><code class="language-js"><span class="token function">getAll</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    axios<span class="token punctuation">.</span><span class="token keyword">get</span><span class="token punctuation">(</span><span class="token string">"/books/"</span><span class="token operator">+</span><span class="token keyword">this</span><span class="token punctuation">.</span>pagination<span class="token punctuation">.</span>currentPage<span class="token operator">+</span><span class="token string">"/"</span><span class="token operator">+</span><span class="token keyword">this</span><span class="token punctuation">.</span>pagination<span class="token punctuation">.</span>pageSize<span class="token punctuation">)</span>          <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span>res<span class="token punctuation">)</span><span class="token operator">=</span><span class="token operator">></span><span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">//console.log(res.data.data);</span>                  <span class="token keyword">this</span><span class="token punctuation">.</span>dataList <span class="token operator">=</span> res<span class="token punctuation">.</span>data<span class="token punctuation">.</span>data<span class="token punctuation">.</span>records<span class="token punctuation">;</span>            <span class="token keyword">this</span><span class="token punctuation">.</span>pagination<span class="token punctuation">.</span>currentPage <span class="token operator">=</span> res<span class="token punctuation">.</span>data<span class="token punctuation">.</span>data<span class="token punctuation">.</span>current<span class="token punctuation">;</span>            <span class="token keyword">this</span><span class="token punctuation">.</span>pagination<span class="token punctuation">.</span>pageSize <span class="token operator">=</span> res<span class="token punctuation">.</span>data<span class="token punctuation">.</span>data<span class="token punctuation">.</span>size<span class="token punctuation">;</span>            <span class="token keyword">this</span><span class="token punctuation">.</span>pagination<span class="token punctuation">.</span>total <span class="token operator">=</span> res<span class="token punctuation">.</span>data<span class="token punctuation">.</span>data<span class="token punctuation">.</span>total<span class="token punctuation">;</span>              <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">,</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>f.对切换页码操作设置调用当前分页操作</strong></p><pre class="line-numbers language-js"><code class="language-js"><span class="token comment" spellcheck="true">//切换页码</span><span class="token function">handleCurrentChange</span><span class="token punctuation">(</span>currentPage<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>pagination<span class="token punctuation">.</span>currentPage <span class="token operator">=</span> currentPage<span class="token punctuation">;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">getAll</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token punctuation">,</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>g.对切换每页显示条数操作设置调用当前记录数操作</strong></p><pre class="line-numbers language-js"><code class="language-js"><span class="token comment" spellcheck="true">//切换每页条数</span><span class="token function">handleSizeChange</span><span class="token punctuation">(</span>pageSize<span class="token punctuation">)</span><span class="token punctuation">{</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>pagination<span class="token punctuation">.</span>pageSize <span class="token operator">=</span> pageSize<span class="token punctuation">;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">getAll</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>总结</strong></p><ol><li>使用el分页组件</li><li>定义分页组件绑定的数据模型</li><li>异步调用获取分页数据</li><li>分页数据页面回显</li></ol><h3 id="⑥删除功能维护"><a href="#⑥删除功能维护" class="headerlink" title="⑥删除功能维护"></a>⑥删除功能维护</h3><p>由于使用了分页功能，当最后一页只有一条数据时，删除操作就会出现BUG，最后一页无数据但是独立展示，对分页查询功能进行后台功能维护，如果当前页码值大于最大页码值，重新执行查询。其实这个问题解决方案很多，这里给出比较简单的一种处理方案</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@GetMapping</span><span class="token punctuation">(</span><span class="token string">"{currentPage}/{pageSize}"</span><span class="token punctuation">)</span><span class="token keyword">public</span> IPage<span class="token operator">&lt;</span>Book<span class="token operator">></span> <span class="token function">getPage</span><span class="token punctuation">(</span><span class="token annotation punctuation">@PathVariable</span> <span class="token keyword">int</span> currentPage<span class="token punctuation">,</span> <span class="token annotation punctuation">@PathVariable</span> <span class="token keyword">int</span> pageSize<span class="token punctuation">)</span><span class="token punctuation">{</span>    IPage<span class="token operator">&lt;</span>Book<span class="token operator">></span> page <span class="token operator">=</span> bookService<span class="token punctuation">.</span><span class="token function">getPage</span><span class="token punctuation">(</span>currentPage<span class="token punctuation">,</span> pageSize<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">//如果当前页码值大于了总页码值，那么重新执行查询操作，使用最大页码值作为当前页码值</span>    <span class="token keyword">if</span><span class="token punctuation">(</span> currentPage <span class="token operator">></span> page<span class="token punctuation">.</span><span class="token function">getPages</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">{</span>          page <span class="token operator">=</span> bookService<span class="token punctuation">.</span><span class="token function">getPage</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">)</span>page<span class="token punctuation">.</span><span class="token function">getPages</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> pageSize<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">return</span> page<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>删除一条数据之后，主键id不连续的问题解决</p><pre class="line-numbers language-sql"><code class="language-sql"><span class="token comment" spellcheck="true">#删除id列</span><span class="token keyword">alter</span> <span class="token keyword">table</span> tbl_book <span class="token keyword">drop</span> id<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">#新增id列,设为主键,并自增</span><span class="token keyword">ALTER</span> <span class="token keyword">TABLE</span> tbl_book <span class="token keyword">ADD</span> id <span class="token keyword">INT</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">PRIMARY</span> <span class="token keyword">KEY</span> <span class="token keyword">AUTO_INCREMENT</span> <span class="token keyword">FIRST</span><span class="token punctuation">;</span> <span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><h3 id="⑦条件查询"><a href="#⑦条件查询" class="headerlink" title="⑦条件查询"></a>⑦条件查询</h3><p>条件查询可以理解为分页查询的时候除了携带分页数据再多带几个数据的查询。这些多带的数据就是查询条件。</p><p>页面发送请求时，两个分页数据仍然使用路径变量，其他条件采用动态拼装url参数的形式传递</p><p><strong>a.页面封装查询条件字段</strong></p><pre class="line-numbers language-js"><code class="language-js">pagination<span class="token punctuation">:</span> <span class="token punctuation">{</span><span class="token comment" spellcheck="true">//分页相关模型数据</span>    currentPage<span class="token punctuation">:</span> <span class="token number">1</span><span class="token punctuation">,</span><span class="token comment" spellcheck="true">//当前页码</span>    pageSize<span class="token punctuation">:</span><span class="token number">10</span><span class="token punctuation">,</span><span class="token comment" spellcheck="true">//每页显示的记录数</span>    total<span class="token punctuation">:</span><span class="token number">0</span><span class="token punctuation">,</span><span class="token comment" spellcheck="true">//总记录数</span>    name<span class="token punctuation">:</span> <span class="token string">""</span><span class="token punctuation">,</span>    type<span class="token punctuation">:</span> <span class="token string">""</span><span class="token punctuation">,</span>    description<span class="token punctuation">:</span> <span class="token string">""</span><span class="token punctuation">}</span><span class="token punctuation">,</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>b.页面添加查询条件字段对应的数据模型绑定名称</strong></p><pre class="line-numbers language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>filter-container<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>el-input</span> <span class="token attr-name">placeholder</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>图书类别<span class="token punctuation">"</span></span> <span class="token attr-name">v-model</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>pagination.type<span class="token punctuation">"</span></span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>filter-item<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>el-input</span> <span class="token attr-name">placeholder</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>图书名称<span class="token punctuation">"</span></span> <span class="token attr-name">v-model</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>pagination.name<span class="token punctuation">"</span></span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>filter-item<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>el-input</span> <span class="token attr-name">placeholder</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>图书描述<span class="token punctuation">"</span></span> <span class="token attr-name">v-model</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>pagination.description<span class="token punctuation">"</span></span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>filter-item<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>el-button</span> <span class="token attr-name">@click</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>getAll()<span class="token punctuation">"</span></span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>dalfBut<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>查询<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>el-button</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>el-button</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>primary<span class="token punctuation">"</span></span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>butT<span class="token punctuation">"</span></span> <span class="token attr-name">@click</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>handleCreate()<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>新建<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>el-button</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>c.将查询条件组织成url参数，添加到请求url地址中</strong></p><pre class="line-numbers language-js"><code class="language-js"><span class="token function">getAll</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>  <span class="token comment" spellcheck="true">//1.获取查询条件,拼接查询条件</span>  param <span class="token operator">=</span> <span class="token string">"?name="</span><span class="token operator">+</span><span class="token keyword">this</span><span class="token punctuation">.</span>pagination<span class="token punctuation">.</span>name<span class="token punctuation">;</span>  param <span class="token operator">+</span><span class="token operator">=</span> <span class="token string">"&amp;type="</span><span class="token operator">+</span><span class="token keyword">this</span><span class="token punctuation">.</span>pagination<span class="token punctuation">.</span>type<span class="token punctuation">;</span>  param <span class="token operator">+</span><span class="token operator">=</span> <span class="token string">"&amp;description="</span><span class="token operator">+</span><span class="token keyword">this</span><span class="token punctuation">.</span>pagination<span class="token punctuation">.</span>description<span class="token punctuation">;</span>  <span class="token comment" spellcheck="true">//console.log(param);</span>  axios<span class="token punctuation">.</span><span class="token keyword">get</span><span class="token punctuation">(</span><span class="token string">"/books/"</span><span class="token operator">+</span><span class="token keyword">this</span><span class="token punctuation">.</span>pagination<span class="token punctuation">.</span>currentPage<span class="token operator">+</span><span class="token string">"/"</span><span class="token operator">+</span><span class="token keyword">this</span><span class="token punctuation">.</span>pagination<span class="token punctuation">.</span>pageSize<span class="token operator">+</span>param<span class="token punctuation">)</span>    <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span>res<span class="token punctuation">)</span><span class="token operator">=</span><span class="token operator">></span><span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">//console.log(res.data.data);</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>pagination<span class="token punctuation">.</span>currentPage <span class="token operator">=</span> res<span class="token punctuation">.</span>data<span class="token punctuation">.</span>data<span class="token punctuation">.</span>current<span class="token punctuation">;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>pagination<span class="token punctuation">.</span>pageSize <span class="token operator">=</span> res<span class="token punctuation">.</span>data<span class="token punctuation">.</span>data<span class="token punctuation">.</span>size<span class="token punctuation">;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>pagination<span class="token punctuation">.</span>total <span class="token operator">=</span> res<span class="token punctuation">.</span>data<span class="token punctuation">.</span>data<span class="token punctuation">.</span>total<span class="token punctuation">;</span>    <span class="token keyword">this</span><span class="token punctuation">.</span>dataList <span class="token operator">=</span> res<span class="token punctuation">.</span>data<span class="token punctuation">.</span>data<span class="token punctuation">.</span>records<span class="token punctuation">;</span>  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">,</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>d.后台代码中定义实体类封查询条件</strong></p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@GetMapping</span><span class="token punctuation">(</span><span class="token string">"{currentPage}/{pageSize}"</span><span class="token punctuation">)</span><span class="token keyword">public</span> IPage<span class="token operator">&lt;</span>Book<span class="token operator">></span> <span class="token function">getPage</span><span class="token punctuation">(</span><span class="token annotation punctuation">@PathVariable</span> <span class="token keyword">int</span> currentPage<span class="token punctuation">,</span> <span class="token annotation punctuation">@PathVariable</span> <span class="token keyword">int</span> pageSize<span class="token punctuation">,</span>Book book<span class="token punctuation">)</span><span class="token punctuation">{</span>    System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"参数=====>"</span><span class="token operator">+</span>book<span class="token punctuation">)</span><span class="token punctuation">;</span>    IPage<span class="token operator">&lt;</span>Book<span class="token operator">></span> page <span class="token operator">=</span> bookService<span class="token punctuation">.</span><span class="token function">getPage</span><span class="token punctuation">(</span>currentPage<span class="token punctuation">,</span> pageSize<span class="token punctuation">,</span> book<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">//如果当前页码值大于了总页码值，那么重新执行查询操作，使用最大页码值作为当前页码值</span>    <span class="token keyword">if</span><span class="token punctuation">(</span> currentPage <span class="token operator">></span> page<span class="token punctuation">.</span><span class="token function">getPages</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">{</span>          page <span class="token operator">=</span> bookService<span class="token punctuation">.</span><span class="token function">getPage</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">)</span>page<span class="token punctuation">.</span><span class="token function">getPages</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> pageSize<span class="token punctuation">,</span> book<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">return</span> page<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>e.对应业务层接口与实现类进行修正</strong></p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">BookService</span> <span class="token punctuation">{</span>    IPage<span class="token operator">&lt;</span>Book<span class="token operator">></span> <span class="token function">getPage</span><span class="token punctuation">(</span><span class="token keyword">int</span> currentPage<span class="token punctuation">,</span><span class="token keyword">int</span> pageSize<span class="token punctuation">,</span>Book book<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Service</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">BookServiceImpl</span>  <span class="token keyword">implements</span> <span class="token class-name">BookService</span> <span class="token punctuation">{</span>  <span class="token annotation punctuation">@Override</span>  <span class="token keyword">public</span> IPage<span class="token operator">&lt;</span>Book<span class="token operator">></span> <span class="token function">getPage</span><span class="token punctuation">(</span><span class="token keyword">int</span> currentPage<span class="token punctuation">,</span> <span class="token keyword">int</span> pageSize<span class="token punctuation">,</span> Book book<span class="token punctuation">)</span> <span class="token punctuation">{</span>      IPage page <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Page</span><span class="token punctuation">(</span>currentPage<span class="token punctuation">,</span>pageSize<span class="token punctuation">)</span><span class="token punctuation">;</span>      LambdaQueryWrapper<span class="token operator">&lt;</span>Book<span class="token operator">></span> lqw <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">LambdaQueryWrapper</span><span class="token operator">&lt;</span>Book<span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      lqw<span class="token punctuation">.</span><span class="token function">like</span><span class="token punctuation">(</span>Strings<span class="token punctuation">.</span><span class="token function">isNotEmpty</span><span class="token punctuation">(</span>book<span class="token punctuation">.</span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span>Book<span class="token operator">:</span><span class="token operator">:</span>getName<span class="token punctuation">,</span>book<span class="token punctuation">.</span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      lqw<span class="token punctuation">.</span><span class="token function">like</span><span class="token punctuation">(</span>Strings<span class="token punctuation">.</span><span class="token function">isNotEmpty</span><span class="token punctuation">(</span>book<span class="token punctuation">.</span><span class="token function">getType</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span>Book<span class="token operator">:</span><span class="token operator">:</span>getType<span class="token punctuation">,</span>book<span class="token punctuation">.</span><span class="token function">getType</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>lqw<span class="token punctuation">.</span><span class="token function">like</span><span class="token punctuation">(</span>Strings<span class="token punctuation">.</span><span class="token function">isNotEmpty</span><span class="token punctuation">(</span>book<span class="token punctuation">.</span><span class="token function">getDescription</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span>Book<span class="token operator">:</span><span class="token operator">:</span>getDescription<span class="token punctuation">,</span>book<span class="token punctuation">.</span><span class="token function">getDescription</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token keyword">return</span> bookDao<span class="token punctuation">.</span><span class="token function">selectPage</span><span class="token punctuation">(</span>page<span class="token punctuation">,</span>lqw<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>总结</strong></p><ol><li>定义查询条件数据模型（当前封装到分页数据模型中）</li><li>异步调用分页功能并通过请求参数传递数据到后台</li></ol><h1 id="Sponsor❤️"><a href="#Sponsor❤️" class="headerlink" title="Sponsor❤️"></a>Sponsor❤️</h1><p>您的支持是我不断前进的动力，如果您感觉本文对您有所帮助的话，可以考虑打赏一下本文，用以维持本博客的运营费用，拒绝白嫖，从你我做起！🥰🥰🥰</p><table>  <tbody>     <tr>         <td style="text-align:center;">支付宝</td>         <td style="text-align:center;">微信</td>     </tr>   <tr>    <td style="text-align:center;" ><img width="200" src="https://jwt1399.top/medias/reward/alipay.png"></td>          <td style="text-align:center;"><img width="200" src="https://jwt1399.top/medias/reward/sponor_wechat.png"></td>       </tr></tbody></table>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;基础篇包含如何创建一个SpringBoot工程、SpringBoot的基础配置语法格式、常见实用技术整合、整合综合应用。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;小简从 0 开始学 Java 知识之 &lt;a</summary>
        
      
    
    
    
    <category term="Spring" scheme="https://jwt1399.top/categories/Spring/"/>
    
    
    <category term="SpringBoot" scheme="https://jwt1399.top/tags/SpringBoot/"/>
    
  </entry>
  
  <entry>
    <title>LeetCode-剑指offer</title>
    <link href="https://jwt1399.top/posts/26733.html"/>
    <id>https://jwt1399.top/posts/26733.html</id>
    <published>2022-08-09T03:00:02.000Z</published>
    <updated>2023-02-25T14:47:28.743Z</updated>
    
    <content type="html"><![CDATA[<p>首刷剑指offer，刷起来还是比较吃力，大多数题需要看题解才能做出来，甚至有的看了题解都不懂😭，我是废物，希望第二次刷的时候大部分题都能自己做出来吧！！！</p><h1 id="剑指Offer"><a href="#剑指Offer" class="headerlink" title="剑指Offer"></a>剑指Offer</h1><table><thead><tr><th align="center">简单</th><th align="center">中等</th><th align="center">困难</th></tr></thead><tbody><tr><td align="center">38道</td><td align="center">31道</td><td align="center">6道</td></tr></tbody></table><h1 id="第-1-天-栈与队列"><a href="#第-1-天-栈与队列" class="headerlink" title="第 1 天 栈与队列"></a>第 1 天 栈与队列</h1><h2 id="09-用两个栈实现队列"><a href="#09-用两个栈实现队列" class="headerlink" title="09. 用两个栈实现队列"></a>09. 用两个栈实现队列</h2><h3 id="题目"><a href="#题目" class="headerlink" title="题目"></a>题目</h3><blockquote><p>用两个栈实现一个队列。队列的声明如下，请实现它的两个函数 appendTail 和 deleteHead ，分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素，deleteHead 操作返回 -1 )</p><pre class="line-numbers language-bash"><code class="language-bash">示例 1：输入：<span class="token punctuation">[</span><span class="token string">"CQueue"</span>,<span class="token string">"appendTail"</span>,<span class="token string">"deleteHead"</span>,<span class="token string">"deleteHead"</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token punctuation">[</span><span class="token punctuation">]</span>,<span class="token punctuation">[</span>3<span class="token punctuation">]</span>,<span class="token punctuation">[</span><span class="token punctuation">]</span>,<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">]</span>输出：<span class="token punctuation">[</span>null,null,3,-1<span class="token punctuation">]</span>示例 2：输入：<span class="token punctuation">[</span><span class="token string">"CQueue"</span>,<span class="token string">"deleteHead"</span>,<span class="token string">"appendTail"</span>,<span class="token string">"appendTail"</span>,<span class="token string">"deleteHead"</span>,<span class="token string">"deleteHead"</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token punctuation">[</span><span class="token punctuation">]</span>,<span class="token punctuation">[</span><span class="token punctuation">]</span>,<span class="token punctuation">[</span>5<span class="token punctuation">]</span>,<span class="token punctuation">[</span>2<span class="token punctuation">]</span>,<span class="token punctuation">[</span><span class="token punctuation">]</span>,<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">]</span>输出：<span class="token punctuation">[</span>null,-1,null,null,5,2<span class="token punctuation">]</span>```<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></blockquote><h3 id="解答"><a href="#解答" class="headerlink" title="解答"></a>解答</h3><ul><li>成员变量：维护两个栈 inStack 和 outStack，其中 inStack 支持队首插入(appendTail)操作，outStack 支持队尾删除(deleteHead)操作</li><li>构造方法（CQueue）：初始化 inStack 和 outStack 为空</li><li>队首插入元素（appendTail）：inStack 直接插入元素</li><li>队尾删除元素（deleteHead）：<ul><li><strong>当 outStack 不为空</strong>： outStack 中仍有已完成倒序的元素，因此直接返回 outStack 的栈顶元素。</li><li><strong>当 outStack 为空，inStack为空：</strong> 即两个栈都为空，无元素，因此返回 -1 。</li><li><strong>当 outStack 为空， inStack不为空，</strong>：将 inStack 里的所有元素弹出插入到 outStack 里，实现元素倒序，并返回栈 outStack 的栈顶元素。</li></ul></li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">CQueue</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">//两个栈，一个出栈，一个入栈</span>    Stack<span class="token operator">&lt;</span>Integer<span class="token operator">></span> inStack<span class="token punctuation">;</span>    Stack<span class="token operator">&lt;</span>Integer<span class="token operator">></span> outStack<span class="token punctuation">;</span>        <span class="token keyword">public</span> <span class="token function">CQueue</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 初始化栈</span>        inStack <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Stack</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        outStack <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Stack</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>        <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">appendTail</span><span class="token punctuation">(</span><span class="token keyword">int</span> value<span class="token punctuation">)</span> <span class="token punctuation">{</span>        inStack<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>        <span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">deleteHead</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">if</span><span class="token punctuation">(</span><span class="token operator">!</span>outStack<span class="token punctuation">.</span><span class="token function">isEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">{</span>            <span class="token keyword">return</span> outStack<span class="token punctuation">.</span><span class="token function">pop</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">else</span><span class="token punctuation">{</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span>inStack<span class="token punctuation">.</span><span class="token function">isEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">{</span>                <span class="token keyword">return</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>            <span class="token comment" spellcheck="true">//将inStack的所有值放入outStack</span>            <span class="token keyword">while</span><span class="token punctuation">(</span><span class="token operator">!</span>inStack<span class="token punctuation">.</span><span class="token function">isEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">{</span>                outStack<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>inStack<span class="token punctuation">.</span><span class="token function">pop</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>            <span class="token keyword">return</span> outStack<span class="token punctuation">.</span><span class="token function">pop</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="30-包含min函数的栈"><a href="#30-包含min函数的栈" class="headerlink" title="30. 包含min函数的栈"></a>30. 包含min函数的栈</h2><h3 id="题目-1"><a href="#题目-1" class="headerlink" title="题目"></a><strong>题目</strong></h3><blockquote><p>定义栈的数据结构，请在该类型中实现一个能够得到栈的最小元素的 min 函数在该栈中，调用 min、push 及 pop 的时间复杂度都是 O(1)。</p><pre class="line-numbers language-bash"><code class="language-bash">示例:MinStack minStack <span class="token operator">=</span> new MinStack<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>minStack.push<span class="token punctuation">(</span>-2<span class="token punctuation">)</span><span class="token punctuation">;</span>minStack.push<span class="token punctuation">(</span>0<span class="token punctuation">)</span><span class="token punctuation">;</span>minStack.push<span class="token punctuation">(</span>-3<span class="token punctuation">)</span><span class="token punctuation">;</span>minStack.min<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>   --<span class="token operator">></span> 返回 -3.minStack.pop<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>minStack.top<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>   --<span class="token operator">></span> 返回 0.minStack.min<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>   --<span class="token operator">></span> 返回 -2.<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></blockquote><h3 id="解答-1"><a href="#解答-1" class="headerlink" title="解答"></a><strong>解答</strong></h3><p>本题难点：将 min() 函数复杂度降为 O(1) ，可通过建立辅助栈实现；设定一个辅助栈，栈顶表示当前数据栈中的最小值，每次有新元素入栈，就将当前最小值入辅助栈。出栈时，辅助栈也要将栈顶元素出栈，这就解决了出栈时的最小值更新问题。</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">MinStack</span> <span class="token punctuation">{</span>     Stack<span class="token operator">&lt;</span>Integer<span class="token operator">></span> dataStack<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 数据栈</span>     Stack<span class="token operator">&lt;</span>Integer<span class="token operator">></span> minStack<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 辅助栈，记录每次有元素进栈后或者出栈后，元素的最小值</span>    <span class="token keyword">public</span> <span class="token function">MinStack</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 初始化辅助栈和数据栈</span>        dataStack <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Stack</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        minStack <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Stack</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">push</span><span class="token punctuation">(</span><span class="token keyword">int</span> x<span class="token punctuation">)</span> <span class="token punctuation">{</span>        dataStack<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>x<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 数据栈，进栈</span>        <span class="token comment" spellcheck="true">// 如果辅助栈为空，或者辅助栈栈顶 > x，则将 x 设置为最小值，即进辅助栈</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>minStack<span class="token punctuation">.</span><span class="token function">isEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">||</span> minStack<span class="token punctuation">.</span><span class="token function">peek</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">></span> x<span class="token punctuation">)</span><span class="token punctuation">{</span>            minStack<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>x<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span><span class="token keyword">else</span><span class="token punctuation">{</span><span class="token comment" spellcheck="true">// 如果辅助栈栈顶 &lt; x, 则继续将上次的最小值再入一次辅助栈</span>            minStack<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>minStack<span class="token punctuation">.</span><span class="token function">peek</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>      <span class="token punctuation">}</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">pop</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        minStack<span class="token punctuation">.</span><span class="token function">pop</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 辅助栈，出栈</span>        dataStack<span class="token punctuation">.</span><span class="token function">pop</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 数据栈，出栈</span>    <span class="token punctuation">}</span>    <span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">top</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">return</span> dataStack<span class="token punctuation">.</span><span class="token function">peek</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">min</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">return</span> minStack<span class="token punctuation">.</span><span class="token function">peek</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h1 id="第-2-天-链表"><a href="#第-2-天-链表" class="headerlink" title="第 2 天 链表"></a>第 2 天 链表</h1><h2 id="06-从尾到头打印链表"><a href="#06-从尾到头打印链表" class="headerlink" title="06. 从尾到头打印链表"></a>06. 从尾到头打印链表</h2><h3 id="题目-2"><a href="#题目-2" class="headerlink" title="题目"></a><strong>题目</strong></h3><blockquote><p>输入一个链表的头节点，从尾到头反过来返回每个节点的值（用数组返回）。</p><pre><code>示例 1：输入：head = [1,3,2]输出：[2,3,1]</code></pre></blockquote><h3 id="解答-2"><a href="#解答-2" class="headerlink" title="解答"></a><strong>解答</strong></h3><p>栈的特点是后进先出，考虑到栈的这一特点，使用栈将链表元素顺序倒置。<br>创建一个栈，将链表的节点都压入该栈，获得栈的大小 size，创建一个数组 out，其大小为 size，从栈内弹出一个节点，将该节点的值存到 out 当中，最后返回 out 。</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">/** * Definition for singly-linked list. * public class ListNode { *     int val; *     ListNode next; *     ListNode(int x) { val = x; } * } */</span><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token function">reversePrint</span><span class="token punctuation">(</span>ListNode head<span class="token punctuation">)</span> <span class="token punctuation">{</span>        Stack<span class="token operator">&lt;</span>ListNode<span class="token operator">></span> stack <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Stack</span><span class="token operator">&lt;</span>ListNode<span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">while</span><span class="token punctuation">(</span>head <span class="token operator">!=</span> null<span class="token punctuation">)</span><span class="token punctuation">{</span>            stack<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>head<span class="token punctuation">)</span><span class="token punctuation">;</span>            head <span class="token operator">=</span> head<span class="token punctuation">.</span>next<span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">int</span> size <span class="token operator">=</span> stack<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">int</span> out<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">int</span><span class="token punctuation">[</span>size<span class="token punctuation">]</span><span class="token punctuation">;</span>        <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> size<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span><span class="token punctuation">{</span>            out<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> stack<span class="token punctuation">.</span><span class="token function">pop</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>val<span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> out<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="24-反转链表"><a href="#24-反转链表" class="headerlink" title="24. 反转链表"></a>24. 反转链表</h2><h3 id="题目-3"><a href="#题目-3" class="headerlink" title="题目"></a><strong>题目</strong></h3><blockquote><p>定义一个函数，输入一个链表的头节点，反转该链表并输出反转后链表的头节点。</p><pre><code>示例:输入: 1-&gt;2-&gt;3-&gt;4-&gt;5-&gt;NULL输出: 5-&gt;4-&gt;3-&gt;2-&gt;1-&gt;NULL</code></pre></blockquote><h3 id="解答-3"><a href="#解答-3" class="headerlink" title="解答"></a><strong>解答</strong></h3><h4 id="方法1：迭代（双指针）"><a href="#方法1：迭代（双指针）" class="headerlink" title="方法1：迭代（双指针）"></a>方法1：迭代（双指针）</h4><p>定义两个指针 pre 和 cur ，在遍历链表时，将当前节点（cur）的 next 指针改为指向前一个节点（pre），完成一次局部反转。指向前一个节点后，与后一节点的联系就断了，因此在更改引用之前，还需要存储后一个节点（temp）。然后 pre 和 cur 同时往后移动一个位置。最后返回新的头引用。</p><table><thead><tr><th><img src="https://img.jwt1399.top/img/202207281850178.png"></th><th><img src="https://img.jwt1399.top/img/202207281850724.png"></th><th><img src="https://img.jwt1399.top/img/202207281850147.png"></th></tr></thead></table><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> ListNode <span class="token function">reverseList</span><span class="token punctuation">(</span>ListNode head<span class="token punctuation">)</span> <span class="token punctuation">{</span>        ListNode pre <span class="token operator">=</span> null<span class="token punctuation">,</span> cur <span class="token operator">=</span> head<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//初始化pre和cur分别指向null和头节点</span>        <span class="token keyword">while</span><span class="token punctuation">(</span>cur <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>              ListNode temp <span class="token operator">=</span> cur<span class="token punctuation">.</span>next<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 暂存后继节点 cur.next</span>            cur<span class="token punctuation">.</span>next <span class="token operator">=</span> pre<span class="token punctuation">;</span>           <span class="token comment" spellcheck="true">// 修改 next 引用指向</span>            pre <span class="token operator">=</span> cur<span class="token punctuation">;</span>                <span class="token comment" spellcheck="true">// pre 暂存 cur，即 pre 移动到下一个节点</span>            cur <span class="token operator">=</span> temp<span class="token punctuation">;</span>               <span class="token comment" spellcheck="true">// cur 访问下一节点</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> pre<span class="token punctuation">;</span>                   <span class="token comment" spellcheck="true">// 返回新的头引用</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="方法2：递归"><a href="#方法2：递归" class="headerlink" title="方法2：递归"></a>方法2：递归</h4><p>使用递归法遍历链表，当越过尾节点后终止递归，在回溯时修改各节点的 <code>next</code> 引用指向。</p><ul><li><p>终止条件：当前节点和后继为空，则返回尾节点  （即反转链表的头节点）；</p></li><li><p>使用递归函数，一直递归到链表的最后一个结点，该结点就是反转后的头结点，记作 rehead</p></li><li><p>此后，每次函数在返回的过程中，让当前结点的下一个结点的 next 指针指向当前节点。</p></li><li><p>同时让当前结点的 next 指针指向 NULL ，从而实现从链表尾部开始的局部反转</p></li><li><p>当递归函数全部出栈后，链表反转完成，返回反转链表的头节点 rehead ；</p></li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> ListNode <span class="token function">reverseList</span><span class="token punctuation">(</span>ListNode head<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>head <span class="token operator">==</span> null <span class="token operator">||</span> head<span class="token punctuation">.</span>next <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token comment" spellcheck="true">// 终止条件</span>            <span class="token keyword">return</span> head<span class="token punctuation">;</span>        <span class="token punctuation">}</span>        ListNode rehead <span class="token operator">=</span> <span class="token function">reverseList</span><span class="token punctuation">(</span>head<span class="token punctuation">.</span>next<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 递归后继节点</span>        head<span class="token punctuation">.</span>next<span class="token punctuation">.</span>next <span class="token operator">=</span> head<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 修改节点引用指向</span>        head<span class="token punctuation">.</span>next <span class="token operator">=</span> null<span class="token punctuation">;</span>        <span class="token keyword">return</span> rehead<span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 返回反转链表的头节点</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="35-复杂链表的复制"><a href="#35-复杂链表的复制" class="headerlink" title="35. 复杂链表的复制"></a>35. 复杂链表的复制</h2><h3 id="题目-4"><a href="#题目-4" class="headerlink" title="题目"></a>题目</h3><blockquote><p>请实现 copyRandomList 函数，复制一个复杂链表。在复杂链表中，每个节点除了有一个 next 指针指向下一个节点，还有一个 random 指针指向链表中的任意节点或者 null。</p><p><strong>示例 1：</strong></p><pre><code>输入：head = [[7,null],[13,0],[11,4],[10,2],[1,0]]输出：[[7,null],[13,0],[11,4],[10,2],[1,0]]</code></pre><p><strong>示例 2：</strong></p><pre><code>输入：head = [[1,1],[2,1]]输出：[[1,1],[2,1]]</code></pre><p>示例 3：</p><pre><code>输入：head = [[3,null],[3,0],[3,null]]输出：[[3,null],[3,0],[3,null]]</code></pre><p>示例 4：</p><pre><code>输入：head = []输出：[]解释：给定的链表为空（空指针），因此返回 null。</code></pre></blockquote><h3 id="解答-4"><a href="#解答-4" class="headerlink" title="解答"></a>解答</h3><h4 id="方法1：哈希表-x2F-Map"><a href="#方法1：哈希表-x2F-Map" class="headerlink" title="方法1：哈希表&#x2F;Map"></a>方法1：哈希表&#x2F;Map</h4><p><strong>算法流程：</strong></p><ul><li>**1.**若头结点 <code>head</code> 为空，直接返回 null</li><li><strong>2.初始化：</strong>定义一个哈希表，键为链表的原节点，值为新复制的节点。节点 <code>cur</code> 指向头节点 <code>head</code></li><li><strong>3.复制链表：</strong>遍历原链表，将复制的节点全部存入哈希表中</li><li><strong>4.构建新链表的引用指向：</strong>再次遍历原链表，构建新链表，确定 next 和 random 指针的指向</li><li><strong>5.返回值：</strong> 新链表的头节点</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> Node <span class="token function">copyRandomList</span><span class="token punctuation">(</span>Node head<span class="token punctuation">)</span> <span class="token punctuation">{</span>          <span class="token comment" spellcheck="true">// 1. 若头结点为空，直接返回 null</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>head <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token keyword">return</span> null<span class="token punctuation">;</span>          <span class="token comment" spellcheck="true">// 2. 初始化</span>        Node cur <span class="token operator">=</span> head<span class="token punctuation">;</span>        Map<span class="token operator">&lt;</span>Node<span class="token punctuation">,</span> Node<span class="token operator">></span> map <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">HashMap</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 3. 复制各节点，并建立 “原节点 -> 新节点” 的 Map 映射</span>        <span class="token keyword">while</span><span class="token punctuation">(</span>cur <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>            map<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span>cur<span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name">Node</span><span class="token punctuation">(</span>cur<span class="token punctuation">.</span>val<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            cur <span class="token operator">=</span> cur<span class="token punctuation">.</span>next<span class="token punctuation">;</span>        <span class="token punctuation">}</span>        cur <span class="token operator">=</span> head<span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 4. 构建新链表的 next 和 random 指向</span>        <span class="token keyword">while</span><span class="token punctuation">(</span>cur <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>            map<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>cur<span class="token punctuation">)</span><span class="token punctuation">.</span>next <span class="token operator">=</span> map<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>cur<span class="token punctuation">.</span>next<span class="token punctuation">)</span><span class="token punctuation">;</span>            map<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>cur<span class="token punctuation">)</span><span class="token punctuation">.</span>random <span class="token operator">=</span> map<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>cur<span class="token punctuation">.</span>random<span class="token punctuation">)</span><span class="token punctuation">;</span>            cur <span class="token operator">=</span> cur<span class="token punctuation">.</span>next<span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 5. 返回新链表的头节点</span>        <span class="token keyword">return</span> map<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>head<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="方法2：拼接-拆分"><a href="#方法2：拼接-拆分" class="headerlink" title="方法2：拼接 + 拆分"></a>方法2：拼接 + 拆分</h4><p><strong>算法流程：</strong></p><ul><li>1.复制各节点，构建拼接链表:</li></ul><p>设原链表为 node1→node2→⋯ ，构建拼接链表：node1→node1<sub>new</sub> →node2→node2<sub>new</sub> →⋯</p><ul><li>2.构建新链表各节点的 random 指向：</li></ul><p>当访问原节点 <code>cur</code> 的随机指向节点 <code>cur.random</code> 时，对应新节点 <code>cur.next</code> 的随机指向节点为 <code>cur.random.next</code> </p><ul><li>3.拆分原 &#x2F; 新链表：</li></ul><p>设置 <code>pre/cur</code> 分别指向原 &#x2F; 新链表头节点，遍历执行 <code>pre.next = pre.next.next</code> 和 <code>cur.next = cur.next.next</code> 将两链表拆分开</p><ul><li>4.返回新链表的头节点 res 即可。</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> Node <span class="token function">copyRandomList</span><span class="token punctuation">(</span>Node head<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>head <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token keyword">return</span> null<span class="token punctuation">;</span>        Node cur <span class="token operator">=</span> head<span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 1. 复制各节点，并构建拼接链表</span>        <span class="token keyword">while</span><span class="token punctuation">(</span>cur <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>            Node tmp <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Node</span><span class="token punctuation">(</span>cur<span class="token punctuation">.</span>val<span class="token punctuation">)</span><span class="token punctuation">;</span>            tmp<span class="token punctuation">.</span>next <span class="token operator">=</span> cur<span class="token punctuation">.</span>next<span class="token punctuation">;</span>            cur<span class="token punctuation">.</span>next <span class="token operator">=</span> tmp<span class="token punctuation">;</span>            cur <span class="token operator">=</span> tmp<span class="token punctuation">.</span>next<span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 2. 构建各新节点的 random 指向</span>        cur <span class="token operator">=</span> head<span class="token punctuation">;</span>        <span class="token keyword">while</span><span class="token punctuation">(</span>cur <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token keyword">if</span><span class="token punctuation">(</span>cur<span class="token punctuation">.</span>random <span class="token operator">!=</span> null<span class="token punctuation">)</span>                cur<span class="token punctuation">.</span>next<span class="token punctuation">.</span>random <span class="token operator">=</span> cur<span class="token punctuation">.</span>random<span class="token punctuation">.</span>next<span class="token punctuation">;</span>            cur <span class="token operator">=</span> cur<span class="token punctuation">.</span>next<span class="token punctuation">.</span>next<span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 3. 拆分两链表</span>        cur <span class="token operator">=</span> head<span class="token punctuation">.</span>next<span class="token punctuation">;</span>        Node pre <span class="token operator">=</span> head<span class="token punctuation">,</span> res <span class="token operator">=</span> head<span class="token punctuation">.</span>next<span class="token punctuation">;</span>        <span class="token keyword">while</span><span class="token punctuation">(</span>cur<span class="token punctuation">.</span>next <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>            pre<span class="token punctuation">.</span>next <span class="token operator">=</span> pre<span class="token punctuation">.</span>next<span class="token punctuation">.</span>next<span class="token punctuation">;</span>            cur<span class="token punctuation">.</span>next <span class="token operator">=</span> cur<span class="token punctuation">.</span>next<span class="token punctuation">.</span>next<span class="token punctuation">;</span>            pre <span class="token operator">=</span> pre<span class="token punctuation">.</span>next<span class="token punctuation">;</span>            cur <span class="token operator">=</span> cur<span class="token punctuation">.</span>next<span class="token punctuation">;</span>        <span class="token punctuation">}</span>        pre<span class="token punctuation">.</span>next <span class="token operator">=</span> null<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 单独处理原链表尾节点</span>        <span class="token keyword">return</span> res<span class="token punctuation">;</span>      <span class="token comment" spellcheck="true">// 返回新链表头节点</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h1 id="第-3-天-字符串"><a href="#第-3-天-字符串" class="headerlink" title="第 3 天 字符串"></a>第 3 天 字符串</h1><h2 id="05-替换空格"><a href="#05-替换空格" class="headerlink" title="05. 替换空格"></a>05. 替换空格</h2><h3 id="题目-5"><a href="#题目-5" class="headerlink" title="题目"></a><strong>题目</strong></h3><blockquote><p>请实现一个函数，把字符串 <code>s</code> 中的每个空格替换成”%20”。</p><pre><code>示例 1：输入：s = &quot;We are happy.&quot;输出：&quot;We%20are%20happy.&quot;</code></pre></blockquote><h3 id="解答-5"><a href="#解答-5" class="headerlink" title="解答"></a><strong>解答</strong></h3><h4 id="方法1：遍历"><a href="#方法1：遍历" class="headerlink" title="方法1：遍历"></a>方法1：遍历</h4><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> String <span class="token function">replaceSpace</span><span class="token punctuation">(</span>String s<span class="token punctuation">)</span> <span class="token punctuation">{</span>        StringBuilder res <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">StringBuilder</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span> <span class="token punctuation">;</span> i <span class="token operator">&lt;</span> s<span class="token punctuation">.</span><span class="token function">length</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span><span class="token punctuation">{</span>            <span class="token keyword">if</span><span class="token punctuation">(</span>s<span class="token punctuation">.</span><span class="token function">charAt</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token string">' '</span><span class="token punctuation">)</span><span class="token punctuation">{</span>                res<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span><span class="token string">"%20"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span><span class="token keyword">else</span><span class="token punctuation">{</span>                res<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span>s<span class="token punctuation">.</span><span class="token function">charAt</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> res<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="方法2：替换"><a href="#方法2：替换" class="headerlink" title="方法2：替换"></a>方法2：替换</h4><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> String <span class="token function">replaceSpace</span><span class="token punctuation">(</span>String s<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">return</span> s<span class="token punctuation">.</span><span class="token function">replace</span><span class="token punctuation">(</span><span class="token string">" "</span><span class="token punctuation">,</span> <span class="token string">"%20"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="58-II-左旋转字符串"><a href="#58-II-左旋转字符串" class="headerlink" title="58 - II. 左旋转字符串"></a>58 - II. 左旋转字符串</h2><h3 id="题目-6"><a href="#题目-6" class="headerlink" title="题目"></a>题目</h3><blockquote><p>字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如，输入字符串”abcdefg”和数字2，该函数将返回左旋转两位得到的结果”cdefgab”。</p><pre><code>示例 1：输入: s = &quot;abcdefg&quot;, k = 2输出: &quot;cdefgab&quot;示例 2：输入: s = &quot;lrloseumgh&quot;, k = 6输出: &quot;umghlrlose&quot;</code></pre></blockquote><h3 id="解答-6"><a href="#解答-6" class="headerlink" title="解答"></a><strong>解答</strong></h3><h4 id="方法1：切片"><a href="#方法1：切片" class="headerlink" title="方法1：切片"></a>方法1：切片</h4><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> String <span class="token function">reverseLeftWords</span><span class="token punctuation">(</span>String s<span class="token punctuation">,</span> <span class="token keyword">int</span> n<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">return</span> s<span class="token punctuation">.</span><span class="token function">substring</span><span class="token punctuation">(</span>n<span class="token punctuation">,</span> s<span class="token punctuation">.</span><span class="token function">length</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">+</span> s<span class="token punctuation">.</span><span class="token function">substring</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> n<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="方法2：遍历"><a href="#方法2：遍历" class="headerlink" title="方法2：遍历"></a>方法2：遍历</h4><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> String <span class="token function">reverseLeftWords</span><span class="token punctuation">(</span>String s<span class="token punctuation">,</span> <span class="token keyword">int</span> n<span class="token punctuation">)</span> <span class="token punctuation">{</span>        StringBuilder res <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">StringBuilder</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> n<span class="token punctuation">;</span> i<span class="token operator">&lt;</span> s<span class="token punctuation">.</span><span class="token function">length</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span><span class="token punctuation">{</span>            res<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span>s<span class="token punctuation">.</span><span class="token function">charAt</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i<span class="token operator">&lt;</span>n<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span><span class="token punctuation">{</span>            res<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span>s<span class="token punctuation">.</span><span class="token function">charAt</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> res<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="方法3：利用求余运算"><a href="#方法3：利用求余运算" class="headerlink" title="方法3：利用求余运算"></a>方法3：利用求余运算</h4><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> String <span class="token function">reverseLeftWords</span><span class="token punctuation">(</span>String s<span class="token punctuation">,</span> <span class="token keyword">int</span> n<span class="token punctuation">)</span> <span class="token punctuation">{</span>        StringBuilder res <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">StringBuilder</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> n<span class="token punctuation">;</span> i <span class="token operator">&lt;</span> n <span class="token operator">+</span> s<span class="token punctuation">.</span><span class="token function">length</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span>            res<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span>s<span class="token punctuation">.</span><span class="token function">charAt</span><span class="token punctuation">(</span>i <span class="token operator">%</span> s<span class="token punctuation">.</span><span class="token function">length</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> res<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h1 id="第-4-天-查找算法-简单"><a href="#第-4-天-查找算法-简单" class="headerlink" title="第 4 天 查找算法(简单)"></a>第 4 天 查找算法(简单)</h1><h2 id="03-数组中重复的数字"><a href="#03-数组中重复的数字" class="headerlink" title="03. 数组中重复的数字"></a>03. 数组中重复的数字</h2><h3 id="题目-7"><a href="#题目-7" class="headerlink" title="题目"></a>题目</h3><blockquote><p>在一个长度为 n 的数组 nums 里的所有数字都在 0～n-1 的范围内。数组中某些数字是重复的，但不知道有几个数字重复了，也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。</p><pre><code>输入：[2, 3, 1, 0, 2, 5, 3]输出：2 或 3 </code></pre></blockquote><h3 id="解答-7"><a href="#解答-7" class="headerlink" title="解答"></a>解答</h3><h4 id="方法1：哈希表-x2F-Set"><a href="#方法1：哈希表-x2F-Set" class="headerlink" title="方法1：哈希表&#x2F;Set"></a>方法1：哈希表&#x2F;Set</h4><p>创建一个 Set ，遍历数组中的每个元素，将该元素加入 Set 中，判断是否添加成功（因为 Set 不允许出现重复元素）</p><ul><li>如果添加失败，说明该元素已经在集合中，因此该元素是重复元素，返回该元素</li><li>反之则没有重复</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">findRepeatNumber</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> nums<span class="token punctuation">)</span> <span class="token punctuation">{</span>        Set<span class="token operator">&lt;</span>Integer<span class="token operator">></span> set <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">HashSet</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> num <span class="token operator">:</span> nums<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>set<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>num<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token keyword">return</span> num<span class="token punctuation">;</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="方法2：索引匹配"><a href="#方法2：索引匹配" class="headerlink" title="方法2：索引匹配"></a>方法2：索引匹配</h4><ul><li>由题意可知：长度为 n 的数组 nums 里的所有数字都在 0～n-1 的范围内</li><li>因此每个元素都可以按照其对应的下标进行存取</li><li>当其对应的下标被相同的元素占用时，则该元素重复</li></ul><p><strong>算法流程：</strong></p><ul><li><p>遍历数组 nums ，设索引初始值为 i &#x3D; 0</p></li><li><p>若 nums[i] &#x3D; i： 说明此数字已在对应索引位置，无需交换，因此跳过</p></li><li><p>若 nums[nums[i]] &#x3D; nums[i]： 代表索引 nums[i] 处和索引 i 处的元素值都为 nums[i] ，即找到一组重复值，返回此值 nums[i] </p></li><li><p>否则： 交换索引为 i 和 nums[i] 的元素值，将此数字交换至对应索引位置</p></li><li><p>若遍历完毕尚未返回，则返回 -1</p></li></ul><p><img src="https://img.jwt1399.top/img/202207282300828.png"></p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">findRepeatNumber</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> nums<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>        <span class="token keyword">while</span><span class="token punctuation">(</span>i <span class="token operator">&lt;</span> nums<span class="token punctuation">.</span>length<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">//当前元素正好在其所对应的位置上</span>            <span class="token keyword">if</span><span class="token punctuation">(</span>nums<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">==</span> i<span class="token punctuation">)</span> <span class="token punctuation">{</span>                i<span class="token operator">++</span><span class="token punctuation">;</span>                <span class="token keyword">continue</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>            <span class="token comment" spellcheck="true">//当前元素所对应的位置的值和当前元素相同，找到重复元素。</span>            <span class="token keyword">if</span><span class="token punctuation">(</span>nums<span class="token punctuation">[</span>nums<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">]</span> <span class="token operator">==</span> nums<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token keyword">return</span> nums<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">//交换两个元素，重复循环，继续对当前下标的元素进行下标匹配。</span>            <span class="token keyword">int</span> tmp <span class="token operator">=</span> nums<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">;</span>            nums<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> nums<span class="token punctuation">[</span>tmp<span class="token punctuation">]</span><span class="token punctuation">;</span>            nums<span class="token punctuation">[</span>tmp<span class="token punctuation">]</span> <span class="token operator">=</span> tmp<span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="53-I-在排序数组中查找数字-I"><a href="#53-I-在排序数组中查找数字-I" class="headerlink" title="53 - I. 在排序数组中查找数字 I"></a>53 - I. 在排序数组中查找数字 I</h2><h3 id="题目-8"><a href="#题目-8" class="headerlink" title="题目"></a>题目</h3><blockquote><p>统计一个数字在排序数组中出现的次数。 </p><p><strong>示例 1:</strong></p><pre><code>输入: nums = [5,7,7,8,8,10], target = 8输出: 2</code></pre><p><strong>示例 2:</strong></p><pre><code>输入: nums = [5,7,7,8,8,10], target = 6输出: 0</code></pre></blockquote><h3 id="解答-8"><a href="#解答-8" class="headerlink" title="解答"></a>解答</h3><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">search</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> nums<span class="token punctuation">,</span> <span class="token keyword">int</span> target<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">int</span> count <span class="token operator">=</span> <span class="token number">0</span> <span class="token punctuation">;</span>        <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i<span class="token operator">&lt;</span>nums<span class="token punctuation">.</span>length<span class="token punctuation">;</span>i<span class="token operator">++</span><span class="token punctuation">)</span><span class="token punctuation">{</span>            <span class="token keyword">if</span><span class="token punctuation">(</span>nums<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">==</span> target<span class="token punctuation">)</span><span class="token punctuation">{</span>                count<span class="token operator">++</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> count<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="53-II-0～n-1中缺失的数字"><a href="#53-II-0～n-1中缺失的数字" class="headerlink" title="53 - II. 0～n-1中缺失的数字"></a>53 - II. 0～n-1中缺失的数字</h2><h3 id="题目-9"><a href="#题目-9" class="headerlink" title="题目"></a>题目</h3><blockquote><p>一个长度为n-1的递增排序数组中的所有数字都是唯一的，并且每个数字都在范围0～n-1之内。在范围0～n-1内的n个数字中有且只有一个数字不在该数组中，请找出这个数字。</p><p><strong>示例 1:</strong></p><pre><code>输入: [0,1,3]输出: 2</code></pre><p><strong>示例 2:</strong></p><pre><code>输入: [0,1,2,3,4,5,6,7,9]输出: 8</code></pre></blockquote><h3 id="解答-9"><a href="#解答-9" class="headerlink" title="解答"></a>解答</h3><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">missingNumber</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> nums<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> nums<span class="token punctuation">.</span>length<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span><span class="token punctuation">{</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span>i <span class="token operator">!=</span> nums<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">)</span>                <span class="token keyword">return</span> i<span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span>  nums<span class="token punctuation">.</span>length<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h1 id="第-5-天-查找算法-中等"><a href="#第-5-天-查找算法-中等" class="headerlink" title="第 5 天 查找算法(中等)"></a>第 5 天 查找算法(中等)</h1><h2 id="04-二维数组中的查找"><a href="#04-二维数组中的查找" class="headerlink" title="04. 二维数组中的查找"></a>04. 二维数组中的查找</h2><h3 id="题目-10"><a href="#题目-10" class="headerlink" title="题目"></a>题目</h3><blockquote><p>在一个 n * m 的二维数组中，每一行都按照从左到右递增的顺序排序，每一列都按照从上到下递增的顺序排序。请完成一个高效的函数，输入这样的一个二维数组和一个整数，判断数组中是否含有该整数。</p><p><strong>示例:</strong></p><p>现有矩阵 matrix 如下：</p><pre><code>[  [1,   4,  7, 11, 15],  [2,   5,  8, 12, 19],  [3,   6,  9, 16, 22],  [10, 13, 14, 17, 24],  [18, 21, 23, 26, 30]]</code></pre><p>给定 target &#x3D; <code>5</code>，返回 <code>true</code>。</p><p>给定 target &#x3D; <code>20</code>，返回 <code>false</code>。</p></blockquote><h3 id="解答-10"><a href="#解答-10" class="headerlink" title="解答"></a>解答</h3><p>算法流程：</p><ul><li>从矩阵 matrix 左下角元素（索引设为 (i, j) ）开始遍历，并与目标值对比：<ul><li>当 <code>matrix[i][j] &gt; target</code> 时，则 <code>target</code> 在该行的上方，执行 <code>i--</code>，即消去该行元素；</li><li>当 <code>matrix[i][j] &lt; target</code> 时，则 <code>target</code> 在该行的右方，执行 <code>j++</code>，即消去该列元素；</li><li>当 <code>matrix[i][j] = target</code> 时，返回 true，代表找到目标值。</li></ul></li><li>若行索引或列索引越界，则代表矩阵中无目标值，返回 false。</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">boolean</span> <span class="token function">findNumberIn2DArray</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token punctuation">]</span> matrix<span class="token punctuation">,</span> <span class="token keyword">int</span> target<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">int</span> i <span class="token operator">=</span> matrix<span class="token punctuation">.</span>length <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">,</span> j <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>        <span class="token keyword">while</span><span class="token punctuation">(</span>i <span class="token operator">>=</span> <span class="token number">0</span> <span class="token operator">&amp;&amp;</span> j <span class="token operator">&lt;</span> matrix<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span>length<span class="token punctuation">)</span><span class="token punctuation">{</span>            <span class="token keyword">if</span><span class="token punctuation">(</span>matrix<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span>j<span class="token punctuation">]</span> <span class="token operator">></span> target<span class="token punctuation">)</span> i<span class="token operator">--</span><span class="token punctuation">;</span>            <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>matrix<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span>j<span class="token punctuation">]</span> <span class="token operator">&lt;</span> target<span class="token punctuation">)</span> j<span class="token operator">++</span><span class="token punctuation">;</span>            <span class="token keyword">else</span> <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="复杂度"><a href="#复杂度" class="headerlink" title="复杂度"></a>复杂度</h3><ul><li><p>时间复杂度： O(M+N)</p><ul><li>M 和 N 分别为矩阵行数和列数，此算法最多循环 M+N 次。</li></ul></li><li><p>空间复杂度： O(1)</p><ul><li>i， j 指针使用常数大小额外空间。</li></ul></li></ul><h2 id="11-旋转数组的最小数字"><a href="#11-旋转数组的最小数字" class="headerlink" title="11. 旋转数组的最小数字"></a>11. 旋转数组的最小数字</h2><h3 id="题目-11"><a href="#题目-11" class="headerlink" title="题目"></a>题目</h3><blockquote><p>把一个数组最开始的若干个元素搬到数组的末尾，我们称之为数组的旋转。</p><p>给你一个可能存在 <strong>重复</strong> 元素值的数组 <code>numbers</code> ，它原来是一个升序排列的数组，并按上述情形进行了一次旋转。请返回旋转数组的<strong>最小元素</strong>。例如，数组 <code>[3,4,5,1,2]</code> 为 <code>[1,2,3,4,5]</code> 的一次旋转，该数组的最小值为 1。 </p><p>注意，数组 <code>[a[0], a[1], a[2], ..., a[n-1]]</code> 旋转一次 的结果为数组 <code>[a[n-1], a[0], a[1], a[2], ..., a[n-2]]</code> 。</p><p><strong>示例 1：</strong></p><pre><code>输入：numbers = [3,4,5,1,2]输出：1</code></pre><p><strong>示例 2：</strong></p><pre><code>输入：numbers = [2,2,2,0,1]输出：0</code></pre></blockquote><h3 id="解答-11"><a href="#解答-11" class="headerlink" title="解答"></a>解答</h3><p>算法流程：</p><ul><li>初始化： 声明 low，high 双指针分别指向 numbers 数组左右两端；</li><li>循环二分： 设 pivot &#x3D; (low + high) &#x2F; 2 为每次二分的中点（ “&#x2F;“ 代表向下取整除法），可分为以下三种情况：<ul><li>当 nums[pivot] &gt; nums[high] 时： 旋转点 x 一定在 [pivot + 1, high] 闭区间内，因此执行 low &#x3D; pivot + 1；</li><li>当 nums[pivot] &lt; nums[high] 时： 旋转点 x 一定在 [low, pivot] 闭区间内，因此执行 high &#x3D; pivot；</li><li>当 nums[pivot] &#x3D; nums[high] 时： 无法判断 pivot 在哪个排序数组中，即无法判断旋转点 x 在 [low, pivot] 还是 [pivot + 1, high] 区间中。解决方案： 执行 high &#x3D; high - 1 缩小判断范围，分析见下文。</li></ul></li><li>返回值： 当 low &#x3D; high 时跳出二分循环，并返回旋转点的值 nums[low] 即可。</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">minArray</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> numbers<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">int</span> low <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>        <span class="token keyword">int</span> high <span class="token operator">=</span> numbers<span class="token punctuation">.</span>length <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">;</span>        <span class="token keyword">while</span> <span class="token punctuation">(</span>low <span class="token operator">&lt;</span> high<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// int pivot = (low + high) / 2;</span>            <span class="token keyword">int</span> pivot <span class="token operator">=</span> low <span class="token operator">+</span> <span class="token punctuation">(</span>high <span class="token operator">-</span> low<span class="token punctuation">)</span> <span class="token operator">/</span> <span class="token number">2</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//防止low + high造成整型越界</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span>numbers<span class="token punctuation">[</span>pivot<span class="token punctuation">]</span> <span class="token operator">&lt;</span> numbers<span class="token punctuation">[</span>high<span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                high <span class="token operator">=</span> pivot<span class="token punctuation">;</span>            <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>numbers<span class="token punctuation">[</span>pivot<span class="token punctuation">]</span> <span class="token operator">></span> numbers<span class="token punctuation">[</span>high<span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                low <span class="token operator">=</span> pivot <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>                high <span class="token operator">-=</span> <span class="token number">1</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> numbers<span class="token punctuation">[</span>low<span class="token punctuation">]</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="复杂度-1"><a href="#复杂度-1" class="headerlink" title="复杂度"></a>复杂度</h3><ul><li><p>时间复杂度：O(log<sub>n</sub>)</p></li><li><p>空间复杂度：O(1)</p></li></ul><h2 id="50-第一个只出现一次的字符"><a href="#50-第一个只出现一次的字符" class="headerlink" title="50. 第一个只出现一次的字符"></a>50. 第一个只出现一次的字符</h2><h3 id="题目-12"><a href="#题目-12" class="headerlink" title="题目"></a>题目</h3><blockquote><p>在字符串 s 中找出第一个只出现一次的字符。如果没有，返回一个单空格。 s 只包含小写字母。</p><p><strong>示例 1:</strong></p><pre><code>输入：s = &quot;abaccdeff&quot;输出：&#39;b&#39;</code></pre><p><strong>示例 2:</strong></p><pre><code>输入：s = &quot;&quot; 输出：&#39; &#39;</code></pre></blockquote><h3 id="解答-12"><a href="#解答-12" class="headerlink" title="解答"></a>解答</h3><h4 id="方法1：哈希表-x2F-Map-1"><a href="#方法1：哈希表-x2F-Map-1" class="headerlink" title="方法1：哈希表&#x2F;Map"></a>方法1：哈希表&#x2F;Map</h4><ol><li>遍历字符串 <code>s</code> ，使用哈希表统计各字符出现次数。</li><li>再遍历字符串 <code>s</code> ，在哈希表中找到首个 “数量为 1 的字符”，并返回。</li></ol><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">char</span> <span class="token function">firstUniqChar</span><span class="token punctuation">(</span>String s<span class="token punctuation">)</span> <span class="token punctuation">{</span>        HashMap<span class="token operator">&lt;</span>Character<span class="token punctuation">,</span>Integer<span class="token operator">></span> map <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">HashMap</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">char</span><span class="token punctuation">[</span><span class="token punctuation">]</span> S <span class="token operator">=</span> s<span class="token punctuation">.</span><span class="token function">toCharArray</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">char</span> i <span class="token operator">:</span> S<span class="token punctuation">)</span>            map<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span>i<span class="token punctuation">,</span>map<span class="token punctuation">.</span><span class="token function">getOrDefault</span><span class="token punctuation">(</span>i<span class="token punctuation">,</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token operator">+</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">char</span> i <span class="token operator">:</span> S<span class="token punctuation">)</span>            <span class="token keyword">if</span><span class="token punctuation">(</span>map<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span><span class="token operator">==</span><span class="token number">1</span><span class="token punctuation">)</span>                <span class="token keyword">return</span> i<span class="token punctuation">;</span>        <span class="token keyword">return</span> <span class="token string">' '</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="方法2：有序哈希表"><a href="#方法2：有序哈希表" class="headerlink" title="方法2：有序哈希表"></a>方法2：有序哈希表</h4><p>在哈希表的基础上，有序哈希表中的键值对是 <strong>按照插入顺序排序</strong> 的。基于此，可通过遍历有序哈希表，实现搜索首个 “数量为 1 的字符”。</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">char</span> <span class="token function">firstUniqChar</span><span class="token punctuation">(</span>String s<span class="token punctuation">)</span> <span class="token punctuation">{</span>        Map<span class="token operator">&lt;</span>Character<span class="token punctuation">,</span> Integer<span class="token operator">></span> map <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">LinkedHashMap</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">char</span><span class="token punctuation">[</span><span class="token punctuation">]</span> S <span class="token operator">=</span> s<span class="token punctuation">.</span><span class="token function">toCharArray</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">char</span> i <span class="token operator">:</span> S<span class="token punctuation">)</span>            map<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span>i<span class="token punctuation">,</span>map<span class="token punctuation">.</span><span class="token function">getOrDefault</span><span class="token punctuation">(</span>i<span class="token punctuation">,</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token operator">+</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">for</span><span class="token punctuation">(</span>Map<span class="token punctuation">.</span>Entry<span class="token operator">&lt;</span>Character<span class="token punctuation">,</span> Integer<span class="token operator">></span> d <span class="token operator">:</span> map<span class="token punctuation">.</span><span class="token function">entrySet</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">{</span>           <span class="token keyword">if</span><span class="token punctuation">(</span>d<span class="token punctuation">.</span><span class="token function">getValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">==</span><span class="token number">1</span><span class="token punctuation">)</span>              <span class="token keyword">return</span> d<span class="token punctuation">.</span><span class="token function">getKey</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> <span class="token string">' '</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="复杂度-2"><a href="#复杂度-2" class="headerlink" title="复杂度"></a>复杂度</h3><p>方法1:</p><ul><li><p>时间复杂度：O(n)，其中 n 是字符串 s 的长度。</p></li><li><p>空间复杂度：O(1)， s 只包含小写字母，因此最多有 26 个不同字符，HashMap 存储需占用 O(26) &#x3D; O(1) 的额外空间。</p></li></ul><p>方法2:</p><p>时间和空间复杂度均与 “方法1” 相同，而具体分析：方法 1 需遍历 s 两轮；方法 2 遍历 s 一轮，遍历 map                 一轮（ map 的长度不大于 26 ）。</p><h1 id="第6天-搜索与回溯算法（简单）"><a href="#第6天-搜索与回溯算法（简单）" class="headerlink" title="第6天 搜索与回溯算法（简单）"></a>第6天 搜索与回溯算法（简单）</h1><h2 id="32-I-从上到下打印二叉树"><a href="#32-I-从上到下打印二叉树" class="headerlink" title="32 - I. 从上到下打印二叉树"></a>32 - I. 从上到下打印二叉树</h2><h3 id="题目-13"><a href="#题目-13" class="headerlink" title="题目"></a>题目</h3><blockquote><p>从上到下打印出二叉树的每个节点，同一层的节点按照从左到右的顺序打印。</p><p>例如:<br>给定二叉树: [3,9,20,null,null,15,7],</p><pre><code>   3  / \ 9  20   /  \  15   7</code></pre><p>返回：</p><p>[3,9,20,15,7]</p></blockquote><h3 id="解答-13"><a href="#解答-13" class="headerlink" title="解答"></a>解答</h3><p>算法流程：</p><ul><li>特例处理： 当树的根节点为空，则直接返回空列表 [] ；</li><li>初始化： 结果列表 res &#x3D; [] ，包含根节点的队列 queue &#x3D; [root] ；</li><li>BFS 循环： 当队列 queue 为空时跳出；<ul><li>出队： 队首元素出队，记为 node；</li><li>打印： 将 node.val 添加至列表 tmp 尾部；</li><li>添加子节点： 若 node 的左（右）子节点不为空，则将左（右）子节点加入队列 queue ；</li></ul></li><li>返回值： 返回打印结果列表 res 即可。</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">/** * Definition for a binary tree node. * public class TreeNode { *     int val; *     TreeNode left; *     TreeNode right; *     TreeNode(int x) { val = x; } * } */</span><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token function">levelOrder</span><span class="token punctuation">(</span>TreeNode root<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>root <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">int</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 空树则返回空数组</span>        Queue<span class="token operator">&lt;</span>TreeNode<span class="token operator">></span> queue <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">LinkedList</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 借助一个队列，通过 BFS 实现按层遍历二叉树</span>        queue<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>root<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 根结点先入队</span>        ArrayList<span class="token operator">&lt;</span>Integer<span class="token operator">></span> tmp <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ArrayList</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 申请一个动态数组 ArrayList 动态添加节点值</span>        <span class="token keyword">while</span><span class="token punctuation">(</span><span class="token operator">!</span>queue<span class="token punctuation">.</span><span class="token function">isEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">{</span>            TreeNode node <span class="token operator">=</span> queue<span class="token punctuation">.</span><span class="token function">poll</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 取出当前队首元素</span>            tmp<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>node<span class="token punctuation">.</span>val<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">if</span><span class="token punctuation">(</span>node<span class="token punctuation">.</span>left <span class="token operator">!=</span> null<span class="token punctuation">)</span> queue<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>node<span class="token punctuation">.</span>left<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 左子节点入队</span>            <span class="token keyword">if</span><span class="token punctuation">(</span>node<span class="token punctuation">.</span>right <span class="token operator">!=</span> null<span class="token punctuation">)</span> queue<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>node<span class="token punctuation">.</span>right<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 右子节点入队</span>        <span class="token punctuation">}</span>             <span class="token comment" spellcheck="true">// 将 ArrayList 转为 int 数组并返回</span>        <span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> res <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">int</span><span class="token punctuation">[</span>tmp<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">;</span>        <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> res<span class="token punctuation">.</span>length<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span>          res<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> tmp<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> res<span class="token punctuation">;</span>      <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="复杂度-3"><a href="#复杂度-3" class="headerlink" title="复杂度"></a>复杂度</h3><ul><li>时间复杂度 O(N) ： N 为二叉树的节点数量，即 BFS 需循环 N 次。</li><li>空间复杂度 O(N)： 最差情况下，即当树为平衡二叉树时，最多有 N&#x2F;2 个树节点同时在 queue 中，使用 O(N) 大小的额外空间。</li></ul><h2 id="32-II-从上到下打印二叉树-II"><a href="#32-II-从上到下打印二叉树-II" class="headerlink" title="32 - II. 从上到下打印二叉树 II"></a>32 - II. 从上到下打印二叉树 II</h2><h3 id="题目-14"><a href="#题目-14" class="headerlink" title="题目"></a>题目</h3><blockquote><p>从上到下按层打印二叉树，同一层的节点按从左到右的顺序打印，每一层打印到一行。</p><p>例如:<br>给定二叉树: [3,9,20,null,null,15,7],</p><pre><code>    3   / \  9  20    /  \   15   7</code></pre><p>返回其层次遍历结果：</p><pre><code>[  [3],  [9,20],  [15,7]]</code></pre></blockquote><h3 id="解答-14"><a href="#解答-14" class="headerlink" title="解答"></a>解答</h3><p>算法流程：</p><ul><li>特例处理： 当根节点为空，则返回空列表 [] ；</li><li>初始化： 打印结果列表 res &#x3D; [] ，包含根节点的队列 queue &#x3D; [root] ；</li><li>BFS 循环： 当队列 queue 为空时跳出；<ul><li>新建一个临时列表 tmp ，用于存储当前层打印结果；</li><li>当前层打印循环： 循环次数为当前层节点数（即队列 queue 长度）；<ul><li>出队： 队首元素出队，记为 node；</li><li>打印： 将 node.val 添加至 tmp 尾部；</li><li>添加子节点： 若 node 的左（右）子节点不为空，则将左（右）子节点加入队列 queue ；</li></ul></li><li>将当前层结果 tmp 添加入 res 。</li></ul></li><li>返回值： 返回打印结果列表 res 即可。</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> List<span class="token operator">&lt;</span>List<span class="token operator">&lt;</span>Integer<span class="token operator">>></span> <span class="token function">levelOrder</span><span class="token punctuation">(</span>TreeNode root<span class="token punctuation">)</span> <span class="token punctuation">{</span>        Queue<span class="token operator">&lt;</span>TreeNode<span class="token operator">></span> queue <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">LinkedList</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        queue<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>root<span class="token punctuation">)</span><span class="token punctuation">;</span>        List<span class="token operator">&lt;</span>List<span class="token operator">&lt;</span>Integer<span class="token operator">>></span> res <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ArrayList</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>root <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token keyword">return</span> res<span class="token punctuation">;</span>        <span class="token keyword">while</span><span class="token punctuation">(</span><span class="token operator">!</span>queue<span class="token punctuation">.</span><span class="token function">isEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            List<span class="token operator">&lt;</span>Integer<span class="token operator">></span> tmp <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ArrayList</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> queue<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> i <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">;</span> i<span class="token operator">--</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                TreeNode node <span class="token operator">=</span> queue<span class="token punctuation">.</span><span class="token function">poll</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                tmp<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>node<span class="token punctuation">.</span>val<span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token keyword">if</span><span class="token punctuation">(</span>node<span class="token punctuation">.</span>left <span class="token operator">!=</span> null<span class="token punctuation">)</span> queue<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>node<span class="token punctuation">.</span>left<span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token keyword">if</span><span class="token punctuation">(</span>node<span class="token punctuation">.</span>right <span class="token operator">!=</span> null<span class="token punctuation">)</span> queue<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>node<span class="token punctuation">.</span>right<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>            res<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>tmp<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> res<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="复杂度-4"><a href="#复杂度-4" class="headerlink" title="复杂度"></a>复杂度</h3><ul><li>时间复杂度 O(N)： N 为二叉树的节点数量，即 BFS 需循环 N 次。</li><li>空间复杂度 O(N) ： 最差情况下，即当树为平衡二叉树时，最多有 N&#x2F;2 个树节点同时在 queue 中，使用 O(N) 大小的额外空间。</li></ul><h2 id="32-III-从上到下打印二叉树-III"><a href="#32-III-从上到下打印二叉树-III" class="headerlink" title="32 - III. 从上到下打印二叉树 III"></a>32 - III. 从上到下打印二叉树 III</h2><h3 id="题目-15"><a href="#题目-15" class="headerlink" title="题目"></a>题目</h3><blockquote><p>请实现一个函数按照之字形顺序打印二叉树，即第一行按照从左到右的顺序打印，第二层按照从右到左的顺序打印，第三行再按照从左到右的顺序打印，其他行以此类推。</p><p>例如:<br>给定二叉树: [3,9,20,null,null,15,7],</p><pre><code>    3   / \  9  20    /  \   15   7</code></pre><p>返回其层次遍历结果：</p><pre><code>[  [3],  [20,9],  [15,7]]</code></pre></blockquote><h3 id="解答-15"><a href="#解答-15" class="headerlink" title="解答"></a>解答</h3><h4 id="方法1：层序遍历-双端队列"><a href="#方法1：层序遍历-双端队列" class="headerlink" title="方法1：层序遍历 + 双端队列"></a>方法1：层序遍历 + 双端队列</h4><p>利用双端队列的两端皆可添加元素的特性，设打印列表（双端队列） tmp ，并规定：</p><ul><li>奇数层 则添加至 tmp 尾部 ，</li><li>偶数层 则添加至 tmp 头部 。</li></ul><p>算法流程：</p><ul><li>特例处理： 当树的根节点为空，则直接返回空列表 [] ；</li><li>初始化： 打印结果空列表 res ，包含根节点的队列 queue ；</li><li>BFS 循环： 当 queue 为空时跳出；<ul><li>新建双端队列 tmp ，用于临时存储当前层打印结果；</li><li>当前层打印循环： 循环次数为当前层节点数（即 queue 长度）；<ul><li>出队： 队首元素出队，记为 node；</li><li>打印： 若为奇数层，将 node.val 添加至 tmp 尾部；否则，添加至 tmp 头部</li><li>添加子节点： 若 node 的左（右）子节点不为空，则加入 queue ；</li></ul></li><li>将当前层结果 tmp 添加入 res 。</li></ul></li><li>返回值： 返回打印结果列表 res 即可；</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">/** * Definition for a binary tree node. * public class TreeNode { *     int val; *     TreeNode left; *     TreeNode right; *     TreeNode(int x) { val = x; } * } */</span><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> List<span class="token operator">&lt;</span>List<span class="token operator">&lt;</span>Integer<span class="token operator">>></span> <span class="token function">levelOrder</span><span class="token punctuation">(</span>TreeNode root<span class="token punctuation">)</span> <span class="token punctuation">{</span>        Queue<span class="token operator">&lt;</span>TreeNode<span class="token operator">></span> queue <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">LinkedList</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        queue<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>root<span class="token punctuation">)</span><span class="token punctuation">;</span>        List<span class="token operator">&lt;</span>List<span class="token operator">&lt;</span>Integer<span class="token operator">>></span> res <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ArrayList</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>root <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token keyword">return</span> res<span class="token punctuation">;</span>        <span class="token keyword">while</span><span class="token punctuation">(</span><span class="token operator">!</span>queue<span class="token punctuation">.</span><span class="token function">isEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            LinkedList<span class="token operator">&lt;</span>Integer<span class="token operator">></span> tmp <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">LinkedList</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//实现双端队列</span>            <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> queue<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> i <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">;</span> i<span class="token operator">--</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                TreeNode node <span class="token operator">=</span> queue<span class="token punctuation">.</span><span class="token function">poll</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token keyword">if</span><span class="token punctuation">(</span>res<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">%</span> <span class="token number">2</span> <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> tmp<span class="token punctuation">.</span><span class="token function">addLast</span><span class="token punctuation">(</span>node<span class="token punctuation">.</span>val<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 奇数层，添加元素至队列尾部</span>                <span class="token keyword">else</span> tmp<span class="token punctuation">.</span><span class="token function">addFirst</span><span class="token punctuation">(</span>node<span class="token punctuation">.</span>val<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 偶数层，添加元素只队列头部</span>                <span class="token keyword">if</span><span class="token punctuation">(</span>node<span class="token punctuation">.</span>left <span class="token operator">!=</span> null<span class="token punctuation">)</span> queue<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>node<span class="token punctuation">.</span>left<span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token keyword">if</span><span class="token punctuation">(</span>node<span class="token punctuation">.</span>right <span class="token operator">!=</span> null<span class="token punctuation">)</span> queue<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>node<span class="token punctuation">.</span>right<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>            res<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>tmp<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> res<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="方法2：层序遍历-倒序"><a href="#方法2：层序遍历-倒序" class="headerlink" title="方法2：层序遍历 + 倒序"></a>方法2：层序遍历 + 倒序</h4><p><strong>偶数层倒序：</strong> 若 <code>res</code> 的长度为 <strong>奇数</strong> ，说明当前是偶数层，则对 <code>tmp</code> 执行 <strong>倒序</strong> 操作。</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> List<span class="token operator">&lt;</span>List<span class="token operator">&lt;</span>Integer<span class="token operator">>></span> <span class="token function">levelOrder</span><span class="token punctuation">(</span>TreeNode root<span class="token punctuation">)</span> <span class="token punctuation">{</span>        Queue<span class="token operator">&lt;</span>TreeNode<span class="token operator">></span> queue <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">LinkedList</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        List<span class="token operator">&lt;</span>List<span class="token operator">&lt;</span>Integer<span class="token operator">>></span> res <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ArrayList</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>root <span class="token operator">!=</span> null<span class="token punctuation">)</span> queue<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>root<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">while</span><span class="token punctuation">(</span><span class="token operator">!</span>queue<span class="token punctuation">.</span><span class="token function">isEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            List<span class="token operator">&lt;</span>Integer<span class="token operator">></span> tmp <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ArrayList</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> queue<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> i <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">;</span> i<span class="token operator">--</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                TreeNode node <span class="token operator">=</span> queue<span class="token punctuation">.</span><span class="token function">poll</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                tmp<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>node<span class="token punctuation">.</span>val<span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token keyword">if</span><span class="token punctuation">(</span>node<span class="token punctuation">.</span>left <span class="token operator">!=</span> null<span class="token punctuation">)</span> queue<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>node<span class="token punctuation">.</span>left<span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token keyword">if</span><span class="token punctuation">(</span>node<span class="token punctuation">.</span>right <span class="token operator">!=</span> null<span class="token punctuation">)</span> queue<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>node<span class="token punctuation">.</span>right<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>            <span class="token keyword">if</span><span class="token punctuation">(</span>res<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">%</span> <span class="token number">2</span> <span class="token operator">==</span> <span class="token number">1</span><span class="token punctuation">)</span> Collections<span class="token punctuation">.</span><span class="token function">reverse</span><span class="token punctuation">(</span>tmp<span class="token punctuation">)</span><span class="token punctuation">;</span>            res<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>tmp<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> res<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="复杂度-5"><a href="#复杂度-5" class="headerlink" title="复杂度"></a>复杂度</h3><h4 id="方法1"><a href="#方法1" class="headerlink" title="方法1"></a>方法1</h4><ul><li><p>时间复杂度 O(N)： N 为二叉树的节点数量，即 BFS 需循环 N 次，占用 O(N)；</p><p>双端队列的队首和队尾的添加和删除操作的时间复杂度均为 O(1)。</p></li><li><p>空间复杂度 O(N) ： 最差情况下，即当树为满二叉树时，最多有N&#x2F;2 个树节点 同时 在 queue 中，使用 O(N) 大小的额外空间。</p></li></ul><h4 id="方法2"><a href="#方法2" class="headerlink" title="方法2"></a>方法2</h4><ul><li><p>时间复杂度 O(N)： N 为二叉树的节点数量，即 BFS 需循环 N 次，占用 O(N)。</p><p>共完成少于 N 个节点的倒序操作，占用 O(N)。</p></li><li><p>空间复杂度 O(N) ： 最差情况下，即当树为满二叉树时，最多有 N&#x2F;2 个树节点同时在 queue 中，使用 O(N) 大小的额外空间。</p></li></ul><h1 id="第-7-天-搜索与回溯算法（简单）"><a href="#第-7-天-搜索与回溯算法（简单）" class="headerlink" title="第 7 天 搜索与回溯算法（简单）"></a>第 7 天 搜索与回溯算法（简单）</h1><h2 id="26-树的子结构"><a href="#26-树的子结构" class="headerlink" title="26.树的子结构"></a>26.树的子结构</h2><h3 id="题目-16"><a href="#题目-16" class="headerlink" title="题目"></a>题目</h3><blockquote><p>输入两棵二叉树A和B，判断B是不是A的子结构。(约定空树不是任意一个树的子结构)</p><p>B是A的子结构， 即 A中有出现和B相同的结构和节点值。</p><p>例如:<br>给定的树 A:</p><pre><code>    3   / \  4   5 / \1   2</code></pre><p>给定的树 B：</p><pre><code>  4  /1</code></pre><p>返回 true，因为 B 与 A 的一个子树拥有相同的结构和节点值。</p><p>示例 1：</p><pre><code>输入：A = [1,2,3], B = [3,1]输出：false</code></pre><p>示例 2：</p><pre><code>输入：A = [3,4,5,1,2], B = [4,1]输出：true</code></pre></blockquote><h3 id="解答-16"><a href="#解答-16" class="headerlink" title="解答"></a>解答</h3><p>算法流程：</p><p><strong><code>recur(A, B)</code> 函数：</strong></p><ol><li><strong>终止条件：</strong><ol><li>当节点 B 为空：说明树 B 已匹配完成（越过叶子节点），因此返回 true ；</li><li>当节点 A 为空：说明已经越过树 A 叶子节点，即匹配失败，返回 false ；</li><li>当节点 A 和 B 的值不同：说明匹配失败，返回 false ；</li></ol></li><li><strong>返回值：</strong><ol><li>判断 A和 B 的<strong>左</strong>子节点是否相等，即 <code>recur(A.left, B.left)</code> ；</li><li>判断 A 和 B 的<strong>右</strong>子节点是否相等，即 <code>recur(A.right, B.right)</code> ；</li></ol></li></ol><p><strong><code>isSubStructure(A, B)</code> 函数：</strong></p><ol><li><strong>特例处理：</strong> 当 树 A 为空 <strong>或</strong> 树 B 为空 时，直接返回 false ；</li><li><strong>返回值：</strong> 若树 B 是树 A 的子结构，则必满足以下三种情况之一，因此用或 <code>||</code> 连接；<ol><li>以 <strong>节点 A 为根节点的子树</strong> 包含树 B ，对应 <code>recur(A, B)</code>；</li><li>树 B 是 <strong>树 A 左子树</strong> 的子结构，对应 <code>isSubStructure(A.left, B)</code>；</li><li>树 B 是 <strong>树 A 右子树</strong> 的子结构，对应 <code>isSubStructure(A.right, B)</code>；</li></ol></li></ol><p><img src="https://img.jwt1399.top/img/202208141004156.png"></p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">/** * Definition for a binary tree node. * public class TreeNode { *     int val; *     TreeNode left; *     TreeNode right; *     TreeNode(int x) { val = x; } * } */</span><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">boolean</span> <span class="token function">isSubStructure</span><span class="token punctuation">(</span>TreeNode A<span class="token punctuation">,</span> TreeNode B<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 若A与B其中一个为空,立即返回false</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>A <span class="token operator">==</span> null <span class="token operator">||</span> B <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// B为A的子结构有3种情况,满足任意一种即可:</span>        <span class="token comment" spellcheck="true">// 1.B的子结构起点为A的根节点,此时结果为recur(A,B)</span>        <span class="token comment" spellcheck="true">// 2.B的子结构起点隐藏在A的左子树中,而不是直接为A的根节点,此时结果为isSubStructure(A.left, B)</span>        <span class="token comment" spellcheck="true">// 3.B的子结构起点隐藏在A的右子树中,此时结果为isSubStructure(A.right, B)</span>        <span class="token keyword">return</span> <span class="token function">recur</span><span class="token punctuation">(</span>A<span class="token punctuation">,</span> B<span class="token punctuation">)</span> <span class="token operator">||</span> <span class="token function">isSubStructure</span><span class="token punctuation">(</span>A<span class="token punctuation">.</span>left<span class="token punctuation">,</span> B<span class="token punctuation">)</span> <span class="token operator">||</span> <span class="token function">isSubStructure</span><span class="token punctuation">(</span>A<span class="token punctuation">.</span>right<span class="token punctuation">,</span> B<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">/*    判断B是否为A的子结构,其中B子结构的起点为A的根节点    */</span>    <span class="token keyword">private</span> <span class="token keyword">boolean</span> <span class="token function">recur</span><span class="token punctuation">(</span>TreeNode A<span class="token punctuation">,</span> TreeNode B<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 若B走完了,说明查找完毕,B为A的子结构</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>B <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 若B不为空并且A为空或者A与B的值不相等,直接可以判断B不是A的子结构</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>A <span class="token operator">==</span> null <span class="token operator">||</span> A<span class="token punctuation">.</span>val <span class="token operator">!=</span> B<span class="token punctuation">.</span>val<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 当A与B当前节点值相等,若要判断B为A的子结构</span>        <span class="token comment" spellcheck="true">// 还需要判断B的左子树是否为A左子树的子结构 &amp;&amp; B的右子树是否为A右子树的子结构</span>        <span class="token comment" spellcheck="true">// 若两者都满足就说明B是A的子结构,并且该子结构以A根节点为起点</span>        <span class="token keyword">return</span> <span class="token function">recur</span><span class="token punctuation">(</span>A<span class="token punctuation">.</span>left<span class="token punctuation">,</span> B<span class="token punctuation">.</span>left<span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> <span class="token function">recur</span><span class="token punctuation">(</span>A<span class="token punctuation">.</span>right<span class="token punctuation">,</span> B<span class="token punctuation">.</span>right<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="复杂度-6"><a href="#复杂度-6" class="headerlink" title="复杂度"></a>复杂度</h3><ul><li><strong>时间复杂度 O(MN)：</strong> 其中 M,N 分别为树 A 和 树 B 的节点数量；先序遍历树 A 占用 O(M) ，每次调用 <code>recur(A, B)</code> 判断占用 O(N)。</li><li><strong>空间复杂度 O(M)：</strong> 当树 A 和树 B 都退化为链表时，递归调用深度最大。当 M ≤ N 时，遍历树 A 与递归判断的总递归深度为 M ；当 M &gt; N 时，最差情况为遍历至树 A 叶子节点，此时总递归深度为 M。</li></ul><h2 id="27-二叉树的镜像"><a href="#27-二叉树的镜像" class="headerlink" title="27.二叉树的镜像"></a>27.二叉树的镜像</h2><h3 id="题目-17"><a href="#题目-17" class="headerlink" title="题目"></a>题目</h3><blockquote><p>请完成一个函数，输入一个二叉树，该函数输出它的镜像。</p><p>例如输入：</p><pre><code>    4  /   \ 2     7/ \   / \</code></pre><p>   1   3 6   9</p><p>镜像输出：</p><pre><code>    4  /   \ 7     2/ \   / \</code></pre><p>   9   6 3   1<br>示例 1：</p><pre><code>输入：root = [4,2,7,1,3,6,9]输出：[4,7,2,9,6,3,1]</code></pre></blockquote><h3 id="解答-17"><a href="#解答-17" class="headerlink" title="解答"></a>解答</h3><p><img src="https://img.jwt1399.top/img/202208131818608.png"></p><h4 id="方法1：递归法"><a href="#方法1：递归法" class="headerlink" title="方法1：递归法"></a>方法1：递归法</h4><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">/** * Definition for a binary tree node. * public class TreeNode { *     int val; *     TreeNode left; *     TreeNode right; *     TreeNode(int x) { val = x; } * } */</span><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> TreeNode <span class="token function">mirrorTree</span><span class="token punctuation">(</span>TreeNode root<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>root <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token keyword">return</span> null<span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">//从上到下直接交换左右子树</span>        TreeNode tmp <span class="token operator">=</span> root<span class="token punctuation">.</span>left<span class="token punctuation">;</span>        root<span class="token punctuation">.</span>left <span class="token operator">=</span> root<span class="token punctuation">.</span>right<span class="token punctuation">;</span>        root<span class="token punctuation">.</span>right <span class="token operator">=</span> tmp<span class="token punctuation">;</span>                <span class="token function">mirrorTree</span><span class="token punctuation">(</span>root<span class="token punctuation">.</span>left<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token function">mirrorTree</span><span class="token punctuation">(</span>root<span class="token punctuation">.</span>right<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> root<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="方法2：辅助栈"><a href="#方法2：辅助栈" class="headerlink" title="方法2：辅助栈"></a>方法2：辅助栈</h4><p>算法流程：</p><ul><li>特例处理： 当 root 为空时，直接返回 null；</li><li>初始化： 栈，并加入根节点 root。</li><li>循环交换： 当栈 stack 为空时跳出；<ul><li>出栈： 记为 node ；</li><li>添加子节点： 将 node 左和右子节点入栈；</li><li>交换： 交换 node 的左 &#x2F; 右子节点。</li></ul></li><li>返回值： 返回根节点 root 。</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> TreeNode <span class="token function">mirrorTree</span><span class="token punctuation">(</span>TreeNode root<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>root <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token keyword">return</span> null<span class="token punctuation">;</span>        Stack<span class="token operator">&lt;</span>TreeNode<span class="token operator">></span> stack <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Stack</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>          stack<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>root<span class="token punctuation">)</span><span class="token punctuation">;</span>         <span class="token keyword">while</span><span class="token punctuation">(</span><span class="token operator">!</span>stack<span class="token punctuation">.</span><span class="token function">isEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            TreeNode node <span class="token operator">=</span> stack<span class="token punctuation">.</span><span class="token function">pop</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">if</span><span class="token punctuation">(</span>node<span class="token punctuation">.</span>left <span class="token operator">!=</span> null<span class="token punctuation">)</span> stack<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>node<span class="token punctuation">.</span>left<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">if</span><span class="token punctuation">(</span>node<span class="token punctuation">.</span>right <span class="token operator">!=</span> null<span class="token punctuation">)</span> stack<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>node<span class="token punctuation">.</span>right<span class="token punctuation">)</span><span class="token punctuation">;</span>            TreeNode tmp <span class="token operator">=</span> node<span class="token punctuation">.</span>left<span class="token punctuation">;</span>            node<span class="token punctuation">.</span>left <span class="token operator">=</span> node<span class="token punctuation">.</span>right<span class="token punctuation">;</span>            node<span class="token punctuation">.</span>right <span class="token operator">=</span> tmp<span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> root<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="复杂度-7"><a href="#复杂度-7" class="headerlink" title="复杂度"></a>复杂度</h3><h4 id="方法1-1"><a href="#方法1-1" class="headerlink" title="方法1"></a>方法1</h4><ul><li>时间复杂度 O(N)： 其中 N 为二叉树的节点数量，建立二叉树镜像需要遍历树的所有节点，占用 O(N) 时间。</li><li>空间复杂度 O(N)： 最差情况下（当二叉树退化为链表），递归时系统需使用 O(N) 大小的栈空间。</li></ul><h4 id="方法2-1"><a href="#方法2-1" class="headerlink" title="方法2"></a>方法2</h4><ul><li>时间复杂度 O(N) ： 其中 N 为二叉树的节点数量，建立二叉树镜像需要遍历树的所有节点，占用 O(N) 时间。</li><li>空间复杂度 O(N) ： 最差情况下，栈 stack 最多同时存储 N+1&#x2F;2个节点，占用 O(N) 额外空间。</li></ul><h2 id="28-对称的二叉树"><a href="#28-对称的二叉树" class="headerlink" title="28.对称的二叉树"></a>28.对称的二叉树</h2><h3 id="题目-18"><a href="#题目-18" class="headerlink" title="题目"></a>题目</h3><blockquote><p>请实现一个函数，用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样，那么它是对称的。</p><p>例如，二叉树 [1,2,2,3,4,4,3] 是对称的。</p><pre><code>   1  / \ 2   2/ \ / \</code></pre><p>   3  4 4  3</p><p>但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的:</p><pre><code>   1  / \ 2   2  \   \  3    3</code></pre><p>示例 1：</p><pre><code>输入：root = [1,2,2,3,4,4,3]输出：true</code></pre><p>示例 2：</p><pre><code>输入：root = [1,2,2,null,3,null,3]输出：false</code></pre></blockquote><h3 id="解答-18"><a href="#解答-18" class="headerlink" title="解答"></a>解答</h3><p>对称二叉树定义： 对于树中 任意两个对称节点 L 和 R ，一定有：</p><ul><li>L.val &#x3D; R.val： 即此两对称节点值相等。</li><li>L.left.val &#x3D; R.right.val：即 L 的左子节点和 R 的右子节点对称；</li><li>L.right.val &#x3D; R.left.val ：即 L 的右子节点和 R 的左子节点对称。</li></ul><p>根据以上规律，考虑从顶至底递归，判断每对节点是否对称，从而判断树是否为对称二叉树。</p><p><img src="https://img.jwt1399.top/img/202208131908122.png"></p><p>算法流程：<br><code>isSymmetric(root)</code> ：</p><ul><li>特例处理： 若根节点 root 为空，则直接返回 true 。</li><li>返回值： 即 isSame(root.left, root.right) ;</li></ul><p><code>isSame(L, R)</code> ：</p><ul><li>终止条件：<ul><li>当 L 和 R 同时越过叶节点： 此树从顶至底的节点都对称，因此返回 true ；</li><li>当 L 或 R 中只有一个越过叶节点： 此树不对称，因此返回 false ；</li><li>当节点 L 值 不等于节点 R 值： 此树不对称，因此返回 false ；</li></ul></li><li>递推工作：<ul><li>判断两节点 L.left 和 R.right 是否对称，即 <code>isSame(L.left, R.right)</code> </li><li>判断两节点 L.right 和 R.left 是否对称，即 <code>isSame(L.right, R.left)</code></li></ul></li><li>返回值： 两对节点都对称时，才是对称树，因此用与逻辑符 &amp;&amp; 连接。</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">boolean</span> <span class="token function">isSymmetric</span><span class="token punctuation">(</span>TreeNode root<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">return</span> root <span class="token operator">==</span> null <span class="token operator">?</span> <span class="token boolean">true</span> <span class="token operator">:</span> <span class="token function">isSame</span><span class="token punctuation">(</span>root<span class="token punctuation">.</span>left<span class="token punctuation">,</span> root<span class="token punctuation">.</span>right<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">boolean</span> <span class="token function">isSame</span><span class="token punctuation">(</span>TreeNode L<span class="token punctuation">,</span> TreeNode R<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>L <span class="token operator">==</span> null <span class="token operator">&amp;&amp;</span> R <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>L <span class="token operator">==</span> null <span class="token operator">||</span> R <span class="token operator">==</span> null <span class="token operator">||</span> L<span class="token punctuation">.</span>val <span class="token operator">!=</span> R<span class="token punctuation">.</span>val<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> <span class="token function">isSame</span><span class="token punctuation">(</span>L<span class="token punctuation">.</span>left<span class="token punctuation">,</span> R<span class="token punctuation">.</span>right<span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> <span class="token function">isSame</span><span class="token punctuation">(</span>L<span class="token punctuation">.</span>right<span class="token punctuation">,</span> R<span class="token punctuation">.</span>left<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="复杂度-8"><a href="#复杂度-8" class="headerlink" title="复杂度"></a>复杂度</h3><p>复杂度分析：</p><ul><li>时间复杂度 O(N) ： 其中 N 为二叉树的节点数量，每次执行 isSame() 可以判断一对节点是否对称，因此最多调用 N&#x2F;2 次 isSame() 方法。</li><li>空间复杂度 O(N)： 最差情况下，二叉树退化为链表，系统使用 O(N) 大小的栈空间。</li></ul><h1 id="第-8-天-动态规划（简单）"><a href="#第-8-天-动态规划（简单）" class="headerlink" title="第 8 天 动态规划（简单）"></a>第 8 天 动态规划（简单）</h1><h2 id="10-I-斐波那契数列"><a href="#10-I-斐波那契数列" class="headerlink" title="10- I. 斐波那契数列"></a>10- I. 斐波那契数列</h2><h3 id="题目-19"><a href="#题目-19" class="headerlink" title="题目"></a>题目</h3><blockquote><p>写一个函数，输入 <code>n</code> ，求斐波那契（Fibonacci）数列的第 <code>n</code> 项（即 <code>F(N)</code>）。斐波那契数列的定义如下：</p><pre><code>F(0) = 0,   F(1) = 1F(N) = F(N - 1) + F(N - 2), 其中 N &gt; 1.</code></pre><p>斐波那契数列由 0 和 1 开始，之后的斐波那契数就是由之前的两数相加而得出。</p><p>答案需要取模 1e9+7（1000000007），如计算初始结果为：1000000008，请返回 1。</p><p><strong>示例 1：</strong></p><pre><code>输入：n = 2输出：1</code></pre><p><strong>示例 2：</strong></p><pre><code>输入：n = 5输出：5</code></pre></blockquote><h3 id="解答-19"><a href="#解答-19" class="headerlink" title="解答"></a>解答</h3><h4 id="方法1：动态规划"><a href="#方法1：动态规划" class="headerlink" title="方法1：动态规划"></a>方法1：动态规划</h4><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">fib</span><span class="token punctuation">(</span><span class="token keyword">int</span> n<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>n <span class="token operator">==</span> <span class="token number">0</span> <span class="token operator">||</span> n<span class="token operator">==</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token keyword">return</span> n<span class="token punctuation">;</span>        <span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> dp <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">int</span><span class="token punctuation">[</span>n <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">;</span>        dp<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>        dp<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>        <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">2</span><span class="token punctuation">;</span> i <span class="token operator">&lt;=</span> n<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span><span class="token punctuation">{</span>            dp<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> dp<span class="token punctuation">[</span>i<span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">+</span> dp<span class="token punctuation">[</span>i<span class="token operator">-</span><span class="token number">2</span><span class="token punctuation">]</span><span class="token punctuation">;</span>            dp<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">%=</span> <span class="token number">1000000007</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> dp<span class="token punctuation">[</span>n<span class="token punctuation">]</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">//优化</span><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">fib</span><span class="token punctuation">(</span><span class="token keyword">int</span> n<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">int</span> fn0 <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">,</span> fn1 <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">,</span> sum<span class="token punctuation">;</span>        <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;=</span> n<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span><span class="token punctuation">{</span>            sum <span class="token operator">=</span> <span class="token punctuation">(</span>fn0 <span class="token operator">+</span> fn1<span class="token punctuation">)</span> <span class="token operator">%</span> <span class="token number">1000000007</span><span class="token punctuation">;</span>            fn0 <span class="token operator">=</span> fn1<span class="token punctuation">;</span>            fn1 <span class="token operator">=</span> sum<span class="token punctuation">;</span>         <span class="token punctuation">}</span>        <span class="token keyword">return</span> fn1<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="方法2：矩阵快速幂"><a href="#方法2：矩阵快速幂" class="headerlink" title="方法2：矩阵快速幂"></a>方法2：矩阵快速幂</h4><p>。。。</p><h3 id="复杂度-9"><a href="#复杂度-9" class="headerlink" title="复杂度"></a>复杂度</h3><h4 id="方法1-2"><a href="#方法1-2" class="headerlink" title="方法1"></a>方法1</h4><ul><li><strong>时间复杂度 O(N) ：</strong> 计算 f(n) 需循环 n 次，每轮循环内计算操作使用 O(1) 。</li><li><strong>空间复杂度 O(1) ：</strong> 几个标志变量使用常数大小的额外空间。</li></ul><h4 id="方法2-2"><a href="#方法2-2" class="headerlink" title="方法2"></a>方法2</h4><ul><li><strong>时间复杂度：O(logn)</strong></li><li><strong>空间复杂度：O(1)</strong></li></ul><h2 id="10-II-青蛙跳台阶问题"><a href="#10-II-青蛙跳台阶问题" class="headerlink" title="10- II. 青蛙跳台阶问题"></a>10- II. 青蛙跳台阶问题</h2><h3 id="题目-20"><a href="#题目-20" class="headerlink" title="题目"></a>题目</h3><blockquote><p>一只青蛙一次可以跳上1级台阶，也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。</p><p>答案需要取模 1e9+7（1000000007），如计算初始结果为：1000000008，请返回 1。</p><p>示例 1：</p><pre><code>输入：n = 2输出：2</code></pre><p>示例 2：</p><pre><code>输入：n = 7输出：21</code></pre><p>示例 3：</p><pre><code>输入：n = 0输出：1</code></pre></blockquote><h3 id="解答-20"><a href="#解答-20" class="headerlink" title="解答"></a>解答</h3><p><img src="https://img.jwt1399.top/img/202208171708144.png"></p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">numWays</span><span class="token punctuation">(</span><span class="token keyword">int</span> n<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>n <span class="token operator">==</span> <span class="token number">0</span> <span class="token operator">||</span> n<span class="token operator">==</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token number">1</span><span class="token punctuation">;</span>        <span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> dp <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">int</span><span class="token punctuation">[</span>n <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">;</span>        dp<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>        dp<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>        <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">2</span><span class="token punctuation">;</span> i <span class="token operator">&lt;=</span> n<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span><span class="token punctuation">{</span>            dp<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> dp<span class="token punctuation">[</span>i<span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">+</span> dp<span class="token punctuation">[</span>i<span class="token operator">-</span><span class="token number">2</span><span class="token punctuation">]</span><span class="token punctuation">;</span>            dp<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">%=</span> <span class="token number">1000000007</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> dp<span class="token punctuation">[</span>n<span class="token punctuation">]</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">//优化</span><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">numWays</span><span class="token punctuation">(</span><span class="token keyword">int</span> n<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">int</span> a <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">,</span> b <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">,</span> sum<span class="token punctuation">;</span>        <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> n<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span><span class="token punctuation">{</span>            sum <span class="token operator">=</span> <span class="token punctuation">(</span>a <span class="token operator">+</span> b<span class="token punctuation">)</span> <span class="token operator">%</span> <span class="token number">1000000007</span><span class="token punctuation">;</span>            a <span class="token operator">=</span> b<span class="token punctuation">;</span>            b <span class="token operator">=</span> sum<span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> a<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="复杂度-10"><a href="#复杂度-10" class="headerlink" title="复杂度"></a>复杂度</h3><ul><li><strong>时间复杂度 O(N) ：</strong> 计算 f(n) 需循环 n 次，每轮循环内计算操作使用 O(1) 。</li><li><strong>空间复杂度 O(1)：</strong> 几个标志变量使用常数大小的额外空间。</li></ul><h2 id="63-股票的最大利润"><a href="#63-股票的最大利润" class="headerlink" title="63.股票的最大利润"></a>63.股票的最大利润</h2><h3 id="题目-21"><a href="#题目-21" class="headerlink" title="题目"></a>题目</h3><blockquote><p>假设把某股票的价格按照时间先后顺序存储在数组中，请问买卖该股票一次可能获得的最大利润是多少？</p><p>示例 1:</p><pre><code>输入: [7,1,5,3,6,4]输出: 5解释: 在第 2 天（股票价格 = 1）的时候买入，在第 5 天（股票价格 = 6）的时候卖出，最大利润 = 6-1 = 5 。注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。</code></pre><p>示例 2:</p><pre><code>输入: [7,6,4,3,1]输出: 0解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。</code></pre></blockquote><h3 id="解答-21"><a href="#解答-21" class="headerlink" title="解答"></a>解答</h3><h4 id="方法1：暴力法"><a href="#方法1：暴力法" class="headerlink" title="方法1：暴力法"></a>方法1：暴力法</h4><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">maxProfit</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> prices<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">int</span> maxprofit <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>        <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> prices<span class="token punctuation">.</span>length <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> j <span class="token operator">=</span> i <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">;</span> j <span class="token operator">&lt;</span> prices<span class="token punctuation">.</span>length<span class="token punctuation">;</span> j<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token keyword">int</span> profit <span class="token operator">=</span> prices<span class="token punctuation">[</span>j<span class="token punctuation">]</span> <span class="token operator">-</span> prices<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">;</span>                <span class="token keyword">if</span> <span class="token punctuation">(</span>profit <span class="token operator">></span> maxprofit<span class="token punctuation">)</span> <span class="token punctuation">{</span>                    maxprofit <span class="token operator">=</span> profit<span class="token punctuation">;</span>                <span class="token punctuation">}</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> maxprofit<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="方法2：动态规划"><a href="#方法2：动态规划" class="headerlink" title="方法2：动态规划"></a>方法2：动态规划</h4><p><img src="https://img.jwt1399.top/img/202208171820656.png"></p><p>前 i 日最大利润 &#x3D; max(前(i−1)日最大利润，第i日价格−前i日最低价格)</p><p><code>dp[i] = max⁡(dp[i−1], prices[i] − min⁡(prices[0:i]))</code></p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">maxProfit</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> prices<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>prices<span class="token punctuation">.</span>length <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span>          <span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> dp <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">int</span><span class="token punctuation">[</span>prices<span class="token punctuation">.</span>length<span class="token punctuation">]</span><span class="token punctuation">;</span>        dp<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>         <span class="token keyword">int</span> min <span class="token operator">=</span> prices<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">;</span>        <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> prices<span class="token punctuation">.</span>length<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span><span class="token punctuation">{</span>            min <span class="token operator">=</span> Math<span class="token punctuation">.</span><span class="token function">min</span><span class="token punctuation">(</span>min<span class="token punctuation">,</span>prices<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            dp<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> Math<span class="token punctuation">.</span><span class="token function">max</span><span class="token punctuation">(</span>dp<span class="token punctuation">[</span>i<span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">,</span>prices<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token operator">-</span>min<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> dp<span class="token punctuation">[</span>prices<span class="token punctuation">.</span>length<span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>效率优化：</p><ul><li><p><strong>时间复杂度降低：</strong> 前 iii 日的最低价格 min⁡(prices[0:i]) 时间复杂度为 O(i)。而在遍历 prices 时，可以借助一个变量（记为成本 cost ）每日更新最低价格。优化后的转移方程为：</p><p><code>dp[i] = max⁡(dp[i−1],prices[i] - min⁡(cost,prices[i])</code></p></li><li><p><strong>空间复杂度降低：</strong> 由于 dp[i] 只与 dp[i−1]， prices[i], cost 相关，因此可使用一个变量（记为利润 profit）代替 dp 列表。优化后的转移方程为：</p><p><code>profit = max⁡(profit,prices[i] − min⁡(cost,prices[i])</code></p></li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">maxProfit</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> prices<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">int</span> cost <span class="token operator">=</span> Integer<span class="token punctuation">.</span>MAX_VALUE<span class="token punctuation">,</span> profit <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>        <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> price <span class="token operator">:</span> prices<span class="token punctuation">)</span> <span class="token punctuation">{</span>            cost <span class="token operator">=</span> Math<span class="token punctuation">.</span><span class="token function">min</span><span class="token punctuation">(</span>cost<span class="token punctuation">,</span> price<span class="token punctuation">)</span><span class="token punctuation">;</span>            profit <span class="token operator">=</span> Math<span class="token punctuation">.</span><span class="token function">max</span><span class="token punctuation">(</span>profit<span class="token punctuation">,</span> price <span class="token operator">-</span> cost<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> profit<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="复杂度-11"><a href="#复杂度-11" class="headerlink" title="复杂度"></a>复杂度</h3><h4 id="方法1-3"><a href="#方法1-3" class="headerlink" title="方法1"></a>方法1</h4><ul><li>时间复杂度：O(n<sup>2</sup>)。循环运行 (n−1)+(n−2)+⋯+2+1=n(n−1)&#x2F;2 次。</li><li>空间复杂度：O(1)。只使用了常数个变量。</li></ul><h4 id="方法2-3"><a href="#方法2-3" class="headerlink" title="方法2"></a>方法2</h4><ul><li>时间复杂度：O(n)，只需要遍历一次。</li><li>空间复杂度：O(1)，只使用了常数个变量。</li></ul><h1 id="第-9-天-动态规划（中等）"><a href="#第-9-天-动态规划（中等）" class="headerlink" title="第 9 天 动态规划（中等）"></a>第 9 天 动态规划（中等）</h1><h2 id="42-连续子数组的最大和"><a href="#42-连续子数组的最大和" class="headerlink" title="42.连续子数组的最大和"></a>42.连续子数组的最大和</h2><h3 id="题目-22"><a href="#题目-22" class="headerlink" title="题目"></a>题目</h3><blockquote><p>输入一个整型数组，数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。</p><p>要求时间复杂度为O(n)。</p><p>示例1:</p><pre><code>输入: nums = [-2,1,-3,4,-1,2,1,-5,4]输出: 6解释: 连续子数组 [4,-1,2,1] 的和最大，为 6。</code></pre></blockquote><h3 id="解答-22"><a href="#解答-22" class="headerlink" title="解答"></a>解答</h3><p><img src="https://img.jwt1399.top/img/202208171820679.png"></p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">maxSubArray</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> nums<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">int</span> dp<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">int</span><span class="token punctuation">[</span>nums<span class="token punctuation">.</span>length<span class="token punctuation">]</span><span class="token punctuation">;</span>        dp<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">=</span> nums<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">;</span>        <span class="token keyword">int</span> res <span class="token operator">=</span> dp<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">;</span>        <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> nums<span class="token punctuation">.</span>length<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span><span class="token punctuation">{</span>            dp<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> Math<span class="token punctuation">.</span><span class="token function">max</span><span class="token punctuation">(</span>dp<span class="token punctuation">[</span>i <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">+</span> nums<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">,</span> nums<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            res <span class="token operator">=</span> Math<span class="token punctuation">.</span><span class="token function">max</span><span class="token punctuation">(</span>res<span class="token punctuation">,</span> dp<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> res<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">//由于 dp[i] 只与 dp[i-1] 和 nums[i] 有关系，因此可以将原数组 nums 用作 dp 列表，即直接在 nums 上修改即可。</span><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">maxSubArray</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> nums<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">int</span> res <span class="token operator">=</span> nums<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">;</span>        <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> nums<span class="token punctuation">.</span>length<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            nums<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">+=</span> Math<span class="token punctuation">.</span><span class="token function">max</span><span class="token punctuation">(</span>nums<span class="token punctuation">[</span>i <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            res <span class="token operator">=</span> Math<span class="token punctuation">.</span><span class="token function">max</span><span class="token punctuation">(</span>res<span class="token punctuation">,</span> nums<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> res<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="复杂度-12"><a href="#复杂度-12" class="headerlink" title="复杂度"></a>复杂度</h3><ul><li><strong>时间复杂度 O(N) ：</strong> 线性遍历数组 nums 即可获得结果，使用 O(N) 时间。</li><li><strong>空间复杂度 O(1) ：</strong> 使用常数大小的额外空间。</li></ul><h2 id="47-礼物的最大价值"><a href="#47-礼物的最大价值" class="headerlink" title="47.礼物的最大价值"></a>47.礼物的最大价值</h2><h3 id="题目-23"><a href="#题目-23" class="headerlink" title="题目"></a>题目</h3><blockquote><p>在一个 m*n 的棋盘的每一格都放有一个礼物，每个礼物都有一定的价值（价值大于 0）。你可以从棋盘的左上角开始拿格子里的礼物，并每次向右或者向下移动一格、直到到达棋盘的右下角。给定一个棋盘及其上面的礼物的价值，请计算你最多能拿到多少价值的礼物？</p><p>示例 1:</p><pre><code>输入: [  [1,3,1],  [1,5,1],  [4,2,1]]输出: 12解释: 路径 1→3→5→2→1 可以拿到最多价值的礼物</code></pre></blockquote><h3 id="解答-23"><a href="#解答-23" class="headerlink" title="解答"></a>解答</h3><p>设动态规划矩阵 dp ，dp(i,j) 代表从棋盘的左上角开始，到达单元格 (i,j) 时能拿到礼物的最大累计价值。</p><ul><li>当 i=0 且 j=0 时，为起始元素；</li><li>当 i=0 且 j≠0 时，为矩阵第一行元素，只可从左边到达；</li><li>当 i≠0 且 j=0 时，为矩阵第一列元素，只可从上边到达；</li><li>当 i≠0 且 j≠0 时，可从左边或上边到达；</li></ul><p><img src="https://img.jwt1399.top/img/202208181212232.png"></p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">maxValue</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token punctuation">]</span> grid<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">int</span> m <span class="token operator">=</span> grid<span class="token punctuation">.</span>length<span class="token punctuation">;</span>        <span class="token keyword">int</span> n <span class="token operator">=</span> grid<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span>length<span class="token punctuation">;</span>        <span class="token keyword">int</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token punctuation">]</span>dp <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">int</span><span class="token punctuation">[</span>m<span class="token punctuation">]</span><span class="token punctuation">[</span>n<span class="token punctuation">]</span><span class="token punctuation">;</span>        <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> m<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span><span class="token punctuation">{</span>            <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> j <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> j <span class="token operator">&lt;</span> n<span class="token punctuation">;</span> j<span class="token operator">++</span><span class="token punctuation">)</span><span class="token punctuation">{</span>                <span class="token keyword">if</span><span class="token punctuation">(</span>i <span class="token operator">==</span> <span class="token number">0</span> <span class="token operator">&amp;&amp;</span> j <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span>                   dp<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span>j<span class="token punctuation">]</span> <span class="token operator">=</span> grid<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">;</span>                <span class="token keyword">else</span> <span class="token keyword">if</span><span class="token punctuation">(</span>i <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span>                     dp<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span>j<span class="token punctuation">]</span> <span class="token operator">=</span> dp<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span>j <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">+</span> grid<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span>j<span class="token punctuation">]</span><span class="token punctuation">;</span>                 <span class="token keyword">else</span> <span class="token keyword">if</span><span class="token punctuation">(</span>j <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span>                      dp<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span>j<span class="token punctuation">]</span> <span class="token operator">=</span> dp<span class="token punctuation">[</span>i <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">[</span>j<span class="token punctuation">]</span> <span class="token operator">+</span> grid<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span>j<span class="token punctuation">]</span><span class="token punctuation">;</span>                 <span class="token keyword">else</span>                    dp<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span>j<span class="token punctuation">]</span> <span class="token operator">=</span> Math<span class="token punctuation">.</span><span class="token function">max</span><span class="token punctuation">(</span>dp<span class="token punctuation">[</span>i <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">[</span>j<span class="token punctuation">]</span><span class="token punctuation">,</span> dp<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span>j <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token operator">+</span> grid<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span>j<span class="token punctuation">]</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> dp<span class="token punctuation">[</span>m <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">[</span>n <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">//优化</span><span class="token comment" spellcheck="true">/**  由于 dp[i][j] 只与 dp[i-1][j] , dp[i][j-1] , grid[i][j]有关系，因此可以将原矩阵 grid 用作 dp 矩阵，即直接在 grid 上修改即可。应用此方法可省去 dp 矩阵使用的额外空间，因此空间复杂度从 O(MN) 降至 O(1)**/</span><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">maxValue</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token punctuation">]</span> grid<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">int</span> m <span class="token operator">=</span> grid<span class="token punctuation">.</span>length<span class="token punctuation">,</span> n <span class="token operator">=</span> grid<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span>length<span class="token punctuation">;</span>        <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> m<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> j <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> j <span class="token operator">&lt;</span> n<span class="token punctuation">;</span> j<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token keyword">if</span><span class="token punctuation">(</span>i <span class="token operator">==</span> <span class="token number">0</span> <span class="token operator">&amp;&amp;</span> j <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">continue</span><span class="token punctuation">;</span>                <span class="token keyword">if</span><span class="token punctuation">(</span>i <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> grid<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span>j<span class="token punctuation">]</span> <span class="token operator">+=</span> grid<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span>j <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">;</span>                <span class="token keyword">else</span> <span class="token keyword">if</span><span class="token punctuation">(</span>j <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> grid<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span>j<span class="token punctuation">]</span> <span class="token operator">+=</span> grid<span class="token punctuation">[</span>i <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">[</span>j<span class="token punctuation">]</span><span class="token punctuation">;</span>                <span class="token keyword">else</span> grid<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span>j<span class="token punctuation">]</span> <span class="token operator">+=</span> Math<span class="token punctuation">.</span><span class="token function">max</span><span class="token punctuation">(</span>grid<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span>j <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">,</span> grid<span class="token punctuation">[</span>i <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">[</span>j<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> grid<span class="token punctuation">[</span>m <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">[</span>n <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">//再优化</span><span class="token comment" spellcheck="true">/**  以上代码逻辑清晰，和转移方程直接对应，但仍可提升效率：当 grid 矩阵很大时， i = 0 或 j = 0 的情况仅占极少数，相当循环每轮都冗余了一次判断。因此，可先初始化矩阵第一行和第一列，再开始遍历递推。**/</span><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">maxValue</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token punctuation">]</span> grid<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">int</span> m <span class="token operator">=</span> grid<span class="token punctuation">.</span>length<span class="token punctuation">,</span> n <span class="token operator">=</span> grid<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span>length<span class="token punctuation">;</span>        <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> j <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> j <span class="token operator">&lt;</span> n<span class="token punctuation">;</span> j<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// 初始化第一行</span>            grid<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">[</span>j<span class="token punctuation">]</span> <span class="token operator">+=</span> grid<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">[</span>j <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">;</span>        <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> m<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// 初始化第一列</span>            grid<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">+=</span> grid<span class="token punctuation">[</span>i <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">;</span>        <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> m<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span>            <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> j <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> j <span class="token operator">&lt;</span> n<span class="token punctuation">;</span> j<span class="token operator">++</span><span class="token punctuation">)</span>                 grid<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span>j<span class="token punctuation">]</span> <span class="token operator">+=</span> Math<span class="token punctuation">.</span><span class="token function">max</span><span class="token punctuation">(</span>grid<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span>j <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">,</span> grid<span class="token punctuation">[</span>i <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">[</span>j<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> grid<span class="token punctuation">[</span>m <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">[</span>n <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="复杂度-13"><a href="#复杂度-13" class="headerlink" title="复杂度"></a>复杂度</h3><ul><li><strong>时间复杂度 O(MN) ：</strong> M,N 分别为矩阵行高、列宽；动态规划需遍历整个 grid 矩阵，使用 O(MN) 时间。</li><li><strong>空间复杂度 O(1) ：</strong> 原地修改使用常数大小的额外空间。</li></ul><h1 id="第-10-天-动态规划（中等）"><a href="#第-10-天-动态规划（中等）" class="headerlink" title="第 10 天 动态规划（中等）"></a>第 10 天 动态规划（中等）</h1><h2 id="46-把数字翻译成字符串"><a href="#46-把数字翻译成字符串" class="headerlink" title="46. 把数字翻译成字符串"></a>46. 把数字翻译成字符串</h2><h3 id="题目-24"><a href="#题目-24" class="headerlink" title="题目"></a>题目</h3><blockquote><p>给定一个数字，我们按照如下规则把它翻译为字符串：0 翻译成 “a” ，1 翻译成 “b”，……，11 翻译成 “l”，……，25 翻译成 “z”。一个数字可能有多个翻译。请编程实现一个函数，用来计算一个数字有多少种不同的翻译方法。</p><p>示例 1:</p><pre><code>输入: 12258输出: 5解释: 12258有5种不同的翻译，分别是&quot;bccfi&quot;, &quot;bwfi&quot;, &quot;bczi&quot;, &quot;mcfi&quot;和&quot;mzi&quot;</code></pre></blockquote><h3 id="解答-24"><a href="#解答-24" class="headerlink" title="解答"></a>解答</h3><p><img src="https://img.jwt1399.top/img/202208181400798.png"></p><p><strong>状态定义：</strong>记数字 num 第 i 位数字为 x<sub>i</sub> ，数字 num 的位数为 n ；dp[i] 代表以 x<sub>i</sub> 为结尾的数字的翻译方案数量。</p><p><strong>转移方程：</strong>可被翻译的两位数区间：当 x<sub>i−1 </sub>&#x3D; 0 时，组成的两位数是无法被翻译的（例如 00,01,02,⋯ ），因此 x<sub>i−1</sub>x<sub>i </sub>的区间为 [10,25] 。</p><p><img src="https://img.jwt1399.top/img/202208181407897.png"></p><p><strong>初始状态：</strong> dp[0] &#x3D; dp[1] &#x3D; 1，即 “无数字” 和 “第 1 位数字” 的翻译方法数量均为 1 </p><blockquote><p>Q： 无数字情况 dp[0] &#x3D; 1 从何而来？<br>A： 当 num 第 1, 2 位的组成的数字 ∈[10,25] 时，显然应有 2 种翻译方法，即 dp[2] &#x3D; dp[1] + dp[0] &#x3D; 2 ，而显然 dp[1] &#x3D; 1 ，因此推出 dp[0] &#x3D; 1 。</p></blockquote><h4 id="方法1：字符串遍历"><a href="#方法1：字符串遍历" class="headerlink" title="方法1：字符串遍历"></a>方法1：<strong>字符串遍历</strong></h4><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">translateNum</span><span class="token punctuation">(</span><span class="token keyword">int</span> num<span class="token punctuation">)</span> <span class="token punctuation">{</span>        String s <span class="token operator">=</span> String<span class="token punctuation">.</span><span class="token function">valueOf</span><span class="token punctuation">(</span>num<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">int</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>dp <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">int</span><span class="token punctuation">[</span>s<span class="token punctuation">.</span><span class="token function">length</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">+</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">;</span>        dp<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">=</span> dp<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>        <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">2</span><span class="token punctuation">;</span> i <span class="token operator">&lt;=</span> s<span class="token punctuation">.</span><span class="token function">length</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span><span class="token punctuation">{</span>            String tmp <span class="token operator">=</span> s<span class="token punctuation">.</span><span class="token function">substring</span><span class="token punctuation">(</span>i <span class="token operator">-</span> <span class="token number">2</span><span class="token punctuation">,</span> i<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">if</span><span class="token punctuation">(</span>tmp<span class="token punctuation">.</span><span class="token function">compareTo</span><span class="token punctuation">(</span><span class="token string">"10"</span><span class="token punctuation">)</span> <span class="token operator">>=</span> <span class="token number">0</span> <span class="token operator">&amp;&amp;</span> tmp<span class="token punctuation">.</span><span class="token function">compareTo</span><span class="token punctuation">(</span><span class="token string">"25"</span><span class="token punctuation">)</span> <span class="token operator">&lt;=</span> <span class="token number">0</span><span class="token punctuation">)</span>                dp<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> dp<span class="token punctuation">[</span>i<span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">+</span> dp<span class="token punctuation">[</span>i<span class="token operator">-</span><span class="token number">2</span><span class="token punctuation">]</span><span class="token punctuation">;</span>            <span class="token keyword">else</span>                dp<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> dp<span class="token punctuation">[</span>i<span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> dp<span class="token punctuation">[</span>s<span class="token punctuation">.</span><span class="token function">length</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">//优化</span><span class="token comment" spellcheck="true">//由于 dp[i]只与 dp[i - 1]和 dp[i - 2]有关，因此可使用变量 a,b,c 分别记录，两变量交替前进即可。此方法可省去 dp 列表使用的 O(N) 的额外空间。</span><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">translateNum</span><span class="token punctuation">(</span><span class="token keyword">int</span> num<span class="token punctuation">)</span> <span class="token punctuation">{</span>        String s <span class="token operator">=</span> String<span class="token punctuation">.</span><span class="token function">valueOf</span><span class="token punctuation">(</span>num<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">int</span> a <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">,</span> b <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>        <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">2</span><span class="token punctuation">;</span> i <span class="token operator">&lt;=</span> s<span class="token punctuation">.</span><span class="token function">length</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            String tmp <span class="token operator">=</span> s<span class="token punctuation">.</span><span class="token function">substring</span><span class="token punctuation">(</span>i <span class="token operator">-</span> <span class="token number">2</span><span class="token punctuation">,</span> i<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">int</span> c <span class="token operator">=</span> tmp<span class="token punctuation">.</span><span class="token function">compareTo</span><span class="token punctuation">(</span><span class="token string">"10"</span><span class="token punctuation">)</span> <span class="token operator">>=</span> <span class="token number">0</span> <span class="token operator">&amp;&amp;</span> tmp<span class="token punctuation">.</span><span class="token function">compareTo</span><span class="token punctuation">(</span><span class="token string">"25"</span><span class="token punctuation">)</span> <span class="token operator">&lt;=</span> <span class="token number">0</span> <span class="token operator">?</span> a <span class="token operator">+</span> b <span class="token operator">:</span> a<span class="token punctuation">;</span>            b <span class="token operator">=</span> a<span class="token punctuation">;</span>            a <span class="token operator">=</span> c<span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> a<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="方法2：数字求余"><a href="#方法2：数字求余" class="headerlink" title="方法2：数字求余"></a>方法2：<strong>数字求余</strong></h4><p>上述方法虽然已经节省了 dp 列表的空间占用，但字符串 s 仍使用了 O(N) 大小的额外空间。</p><pre class="line-numbers language-java"><code class="language-java"><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><h3 id="复杂度-14"><a href="#复杂度-14" class="headerlink" title="复杂度"></a>复杂度</h3><h4 id="方法1-4"><a href="#方法1-4" class="headerlink" title="方法1"></a>方法1</h4><ul><li><p><strong>时间复杂度 O(N) ：</strong> N 为字符串 s 的长度（即数字 num 的位数 log⁡(num) ），其决定了循环次数。</p></li><li><p><strong>空间复杂度 O(N)：</strong> 字符串 s 和 dp 分别使用 O(N) 大小的额外空间。</p></li></ul><h4 id="方法2-4"><a href="#方法2-4" class="headerlink" title="方法2"></a>方法2</h4><ul><li><strong>时间复杂度 O(N) ：</strong> 同方法1</li><li><strong>空间复杂度 O(1) ：</strong> 几个变量使用常数大小的额外空间。</li></ul><h2 id="48-最长不含重复字符的子字符"><a href="#48-最长不含重复字符的子字符" class="headerlink" title="48. 最长不含重复字符的子字符"></a>48. 最长不含重复字符的子字符</h2><h3 id="题目-25"><a href="#题目-25" class="headerlink" title="题目"></a>题目</h3><blockquote><p>请从字符串中找出一个最长的不包含重复字符的子字符串，计算该最长子字符串的长度。</p><p>示例 1:</p><p>输入: “abcabcbb”<br>输出: 3<br>解释: 因为无重复字符的最长子串是 “abc”，所以其长度为 3。<br>示例 2:</p><p>输入: “bbbbb”<br>输出: 1<br>解释: 因为无重复字符的最长子串是 “b”，所以其长度为 1。<br>示例 3:</p><p>输入: “pwwkew”<br>输出: 3<br>解释: 因为无重复字符的最长子串是 “wke”，所以其长度为 3。<br>请注意，你的答案必须是 子串 的长度，”pwke” 是一个子序列，不是子串。</p></blockquote><h3 id="解答-25"><a href="#解答-25" class="headerlink" title="解答"></a>解答</h3><p><img src="https://img.jwt1399.top/img/202208192027792.png"></p><p>由于返回值是取 dp 列表最大值，因此可借助变量 tmp 存储 dp[j]，变量 res 每轮更新最大值。此优化可节省 dp 列表使用的 O(N) 大小的额外空间。</p><h4 id="方法1：动态规划-哈希表"><a href="#方法1：动态规划-哈希表" class="headerlink" title="方法1：动态规划 + 哈希表"></a><strong>方法1：动态规划 + 哈希表</strong></h4><ul><li>哈希表统计： 遍历字符串 s 时，使用哈希表（记为 dic ）统计各字符最后一次出现的索引位置</li><li>左边界 i 获取方式： 遍历到 s[j] 时，可通过访问哈希表 dic[s[j]] 获取最近的相同字符的索引 i</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">lengthOfLongestSubstring</span><span class="token punctuation">(</span>String s<span class="token punctuation">)</span> <span class="token punctuation">{</span>        Map<span class="token operator">&lt;</span>Character<span class="token punctuation">,</span>Integer<span class="token operator">></span> dic <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">HashMap</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">int</span> tmp <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">,</span> res <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>        <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> j <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> j <span class="token operator">&lt;</span> s<span class="token punctuation">.</span><span class="token function">length</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> j<span class="token operator">++</span><span class="token punctuation">)</span><span class="token punctuation">{</span>            <span class="token keyword">int</span> i <span class="token operator">=</span> dic<span class="token punctuation">.</span><span class="token function">getOrDefault</span><span class="token punctuation">(</span>s<span class="token punctuation">.</span><span class="token function">charAt</span><span class="token punctuation">(</span>j<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 获取索引 i</span>            dic<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span>s<span class="token punctuation">.</span><span class="token function">charAt</span><span class="token punctuation">(</span>j<span class="token punctuation">)</span><span class="token punctuation">,</span>j<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 更新哈希表</span>            <span class="token keyword">if</span><span class="token punctuation">(</span>tmp <span class="token operator">&lt;</span> j <span class="token operator">-</span> i<span class="token punctuation">)</span> tmp <span class="token operator">=</span> tmp <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">;</span>            <span class="token keyword">else</span> tmp <span class="token operator">=</span> j <span class="token operator">-</span> i<span class="token punctuation">;</span>            res <span class="token operator">=</span> Math<span class="token punctuation">.</span><span class="token function">max</span><span class="token punctuation">(</span>tmp<span class="token punctuation">,</span>res<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// max(dp[j - 1], dp[j])</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> res<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>Java 的 getOrDefault(key, default)， 代表当哈希表包含键 key 时返回对应 value ，不包含时返回默认值 default</p><h3 id="复杂度-15"><a href="#复杂度-15" class="headerlink" title="复杂度"></a>复杂度</h3><p>时间复杂度 O(N) ： 其中 N 为字符串长度，动态规划需遍历计算 dp 列表。<br>空间复杂度 O(1)： 字符的 ASCII 码范围为 00 ~ 127 ，哈希表 dic 最多使用 O(128) &#x3D; O(1) 大小的额外空间。</p><h1 id="第-11-天-双指针（简单）"><a href="#第-11-天-双指针（简单）" class="headerlink" title="第 11 天 双指针（简单）"></a>第 11 天 双指针（简单）</h1><h2 id="18-删除链表的节点"><a href="#18-删除链表的节点" class="headerlink" title="18. 删除链表的节点"></a>18. 删除链表的节点</h2><h3 id="题目-26"><a href="#题目-26" class="headerlink" title="题目"></a>题目</h3><blockquote><p>给定单向链表的头指针和一个要删除的节点的值，定义一个函数删除该节点。</p><p>返回删除后的链表的头节点。</p><p>注意：此题对比原题有改动</p><p>示例 1:</p><pre><code>输入: head = [4,5,1,9], val = 5输出: [4,1,9]解释: 给定你链表中值为 5 的第二个节点，那么在调用了你的函数之后，该链表应变为 4 -&gt; 1 -&gt; 9.</code></pre><p>示例 2:</p><pre><code>输入: head = [4,5,1,9], val = 1输出: [4,5,9]解释: 给定你链表中值为 1 的第三个节点，那么在调用了你的函数之后，该链表应变为 4 -&gt; 5 -&gt; 9.</code></pre></blockquote><h3 id="解答-26"><a href="#解答-26" class="headerlink" title="解答"></a>解答</h3><p><img src="https://img.jwt1399.top/img/202208192027335.png"></p><h5 id="算法流程："><a href="#算法流程：" class="headerlink" title="算法流程："></a>算法流程：</h5><ol><li><strong>特例处理：</strong> 当应删除头节点 <code>head</code> 时，直接返回 <code>head.next</code> 即可。</li><li><strong>初始化：</strong> <code>pre = head</code> , <code>cur = head.next</code> 。</li><li><strong>定位节点：</strong> 当 <code>cur</code> 为空 <strong>或</strong> <code>cur</code> 节点值等于 <code>val</code> 时跳出。<ol><li>保存当前节点索引，即 <code>pre = cur</code> 。</li><li>遍历下一节点，即 <code>cur = cur.next</code> 。</li></ol></li><li><strong>删除节点：</strong> 若 <code>cur</code> 指向待删除节点，则执行 <code>pre.next = cur.next</code> ；若 <code>cur</code> 指向 null，代表链表中不包含值为 <code>val</code> 的节点。</li><li><strong>返回值：</strong> 返回链表头部节点 <code>head</code> 即可。</li></ol><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">/** * Definition for singly-linked list. * public class ListNode { *     int val; *     ListNode next; *     ListNode(int x) { val = x; } * } */</span><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> ListNode <span class="token function">deleteNode</span><span class="token punctuation">(</span>ListNode head<span class="token punctuation">,</span> <span class="token keyword">int</span> val<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>head<span class="token punctuation">.</span>val <span class="token operator">==</span> val<span class="token punctuation">)</span> <span class="token keyword">return</span> head<span class="token punctuation">.</span>next<span class="token punctuation">;</span>        ListNode pre <span class="token operator">=</span> head<span class="token punctuation">,</span> cur <span class="token operator">=</span> head<span class="token punctuation">.</span>next<span class="token punctuation">;</span>        <span class="token keyword">while</span><span class="token punctuation">(</span>cur <span class="token operator">!=</span> null <span class="token operator">&amp;&amp;</span> cur<span class="token punctuation">.</span>val <span class="token operator">!=</span> val<span class="token punctuation">)</span><span class="token punctuation">{</span>          pre <span class="token operator">=</span> cur<span class="token punctuation">;</span>          cur <span class="token operator">=</span> cur<span class="token punctuation">.</span>next<span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>cur<span class="token punctuation">.</span>val <span class="token operator">==</span> val<span class="token punctuation">)</span>            pre<span class="token punctuation">.</span>next <span class="token operator">=</span> cur<span class="token punctuation">.</span>next<span class="token punctuation">;</span>        <span class="token keyword">return</span> head<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="复杂度-16"><a href="#复杂度-16" class="headerlink" title="复杂度"></a>复杂度</h3><ul><li><strong>时间复杂度 O(N)：</strong> N 为链表长度，删除操作平均需循环 N&#x2F;2 次，最差 N 次。</li><li><strong>空间复杂度 O(1) ：</strong> <code>cur</code>, <code>pre</code> 占用常数大小额外空间。</li></ul><h2 id="22-链表中倒数第k个节点"><a href="#22-链表中倒数第k个节点" class="headerlink" title="22. 链表中倒数第k个节点"></a>22. 链表中倒数第k个节点</h2><h3 id="题目-27"><a href="#题目-27" class="headerlink" title="题目"></a>题目</h3><blockquote><p>输入一个链表，输出该链表中倒数第k个节点。为了符合大多数人的习惯，本题从1开始计数，即链表的尾节点是倒数第1个节点。</p><p>例如，一个链表有 6 个节点，从头节点开始，它们的值依次是 1、2、3、4、5、6。这个链表的倒数第 3 个节点是值为 4 的节点。</p><p>示例：</p><pre><code>给定一个链表: 1-&gt;2-&gt;3-&gt;4-&gt;5, 和 k = 2.返回链表 4-&gt;5.</code></pre></blockquote><h3 id="解答-27"><a href="#解答-27" class="headerlink" title="解答"></a>解答</h3><p>第一时间想到的解法：</p><ul><li>先遍历统计链表长度，记为 n ；</li><li>设置一个指针走 (n-k)步，即可找到链表倒数第 k 个节点。</li></ul><p><img src="https://img.jwt1399.top/img/202208201051016.png"></p><p>使用双指针则可以不用统计链表长度。</p><p><img src="https://img.jwt1399.top/img/202208201051503.png"></p><h5 id="算法流程：-1"><a href="#算法流程：-1" class="headerlink" title="算法流程："></a>算法流程：</h5><ol><li><strong>初始化：</strong> 前指针 <code>former</code> 、后指针 <code>latter</code> ，双指针都指向头节点 <code>head</code> </li><li><strong>构建双指针距离：</strong> 前指针 <code>former</code> 先向前走 k 步（结束后，双指针 <code>former</code> 和 <code>latter</code> 间相距 k 步）。</li><li><strong>双指针共同移动：</strong> 循环中，双指针 <code>former</code> 和 <code>latter</code> 每轮都向前走一步，直至 <code>former</code> 走过链表 <strong>尾节点</strong> 时跳出（跳出后， <code>latter</code> 与尾节点距离为 k−1，即 <code>latter</code> 指向倒数第 k 个节点）。</li><li><strong>返回值：</strong> 返回 <code>latter</code> 即可。</li></ol><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">/** * Definition for singly-linked list. * public class ListNode { *     int val; *     ListNode next; *     ListNode(int x) { val = x; } * } */</span><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> ListNode <span class="token function">getKthFromEnd</span><span class="token punctuation">(</span>ListNode head<span class="token punctuation">,</span> <span class="token keyword">int</span> k<span class="token punctuation">)</span> <span class="token punctuation">{</span>      ListNode former <span class="token operator">=</span> head<span class="token punctuation">,</span>latter <span class="token operator">=</span> head<span class="token punctuation">;</span>      <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> k<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span><span class="token punctuation">{</span>        former <span class="token operator">=</span> former<span class="token punctuation">.</span>next<span class="token punctuation">;</span>      <span class="token punctuation">}</span>      <span class="token keyword">while</span><span class="token punctuation">(</span>former <span class="token operator">!=</span> null<span class="token punctuation">)</span><span class="token punctuation">{</span>        former <span class="token operator">=</span> former<span class="token punctuation">.</span>next<span class="token punctuation">;</span>        latter <span class="token operator">=</span> latter<span class="token punctuation">.</span>next<span class="token punctuation">;</span>      <span class="token punctuation">}</span>      <span class="token keyword">return</span> latter<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="复杂度-17"><a href="#复杂度-17" class="headerlink" title="复杂度"></a>复杂度</h3><ul><li><strong>时间复杂度 O(N) ：</strong> N 为链表长度；总体看， <code>former</code> 走了 N 步， <code>latter</code> 走了 (N−k) 步。</li><li><strong>空间复杂度 O(1) ：</strong> 双指针 <code>former</code> , <code>latter</code> 使用常数大小的额外空间。</li></ul><h1 id="第-12-天-双指针（简单）"><a href="#第-12-天-双指针（简单）" class="headerlink" title="第 12 天 双指针（简单）"></a>第 12 天 双指针（简单）</h1><h2 id="25-合并两个排序的链表"><a href="#25-合并两个排序的链表" class="headerlink" title="25. 合并两个排序的链表"></a>25. 合并两个排序的链表</h2><h3 id="题目-28"><a href="#题目-28" class="headerlink" title="题目"></a>题目</h3><blockquote><p>输入两个递增排序的链表，合并这两个链表并使新链表中的节点仍然是递增排序的。</p><p>示例1：</p><pre><code>输入：1-&gt;2-&gt;4, 1-&gt;3-&gt;4输出：1-&gt;1-&gt;2-&gt;3-&gt;4-&gt;4</code></pre></blockquote><h3 id="解答-28"><a href="#解答-28" class="headerlink" title="解答"></a>解答</h3><p><img src="https://img.jwt1399.top/img/202208201109218.png"></p><h5 id="算法流程：-2"><a href="#算法流程：-2" class="headerlink" title="算法流程："></a>算法流程：</h5><ol><li><strong>初始化：</strong> 伪头节点 dum ，节点 cur 指向 dum 。</li><li><strong>循环合并：</strong> 当 l1 或 l2 为空时跳出；<ol><li>当 l1.val &lt; l2.val 时： cur 的后继节点指定为 l1，并且 l1 向前走一步；</li><li>当 l1.val ≥ l2.val 时： cur 的后继节点指定为 l2，并且 l2 向前走一步 ；</li><li>节点 cur 向前走一步，即 cur = cur.next。</li></ol></li><li><strong>合并剩余尾部：</strong> 跳出时有两种情况，即 l1 为空 <strong>或</strong> l2 为空。<ol><li>若 l1 ≠ null ： 将 l1 添加至节点 cur 之后；</li><li>否则： 将 l2 添加至节点 cur 之后。</li></ol></li><li><strong>返回值：</strong> 合并链表在伪头节点 dum 之后，因此返回 dum.next 即可。</li></ol><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">/** * Definition for singly-linked list. * public class ListNode { *     int val; *     ListNode next; *     ListNode(int x) { val = x; } * } */</span><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> ListNode <span class="token function">mergeTwoLists</span><span class="token punctuation">(</span>ListNode l1<span class="token punctuation">,</span> ListNode l2<span class="token punctuation">)</span> <span class="token punctuation">{</span>     ListNode dum <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ListNode</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">,</span> cur <span class="token operator">=</span> dum<span class="token punctuation">;</span>      <span class="token keyword">while</span><span class="token punctuation">(</span>l1 <span class="token operator">!=</span> null <span class="token operator">&amp;&amp;</span> l2 <span class="token operator">!=</span> null<span class="token punctuation">)</span><span class="token punctuation">{</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>l1<span class="token punctuation">.</span>val <span class="token operator">&lt;</span> l2<span class="token punctuation">.</span>val<span class="token punctuation">)</span> <span class="token punctuation">{</span>          cur<span class="token punctuation">.</span>next <span class="token operator">=</span> l1<span class="token punctuation">;</span>          l1 <span class="token operator">=</span> l1<span class="token punctuation">.</span>next<span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">else</span><span class="token punctuation">{</span>           cur<span class="token punctuation">.</span>next <span class="token operator">=</span> l2<span class="token punctuation">;</span>           l2 <span class="token operator">=</span> l2<span class="token punctuation">.</span>next<span class="token punctuation">;</span>        <span class="token punctuation">}</span>        cur <span class="token operator">=</span> cur<span class="token punctuation">.</span>next<span class="token punctuation">;</span>      <span class="token punctuation">}</span>      <span class="token keyword">if</span><span class="token punctuation">(</span>l1 <span class="token operator">!=</span> null<span class="token punctuation">)</span> cur<span class="token punctuation">.</span>next <span class="token operator">=</span> l1<span class="token punctuation">;</span>      <span class="token keyword">if</span><span class="token punctuation">(</span>l2 <span class="token operator">!=</span> null<span class="token punctuation">)</span> cur<span class="token punctuation">.</span>next <span class="token operator">=</span> l2<span class="token punctuation">;</span>      <span class="token keyword">return</span> dum<span class="token punctuation">.</span>next<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="复杂度-18"><a href="#复杂度-18" class="headerlink" title="复杂度"></a>复杂度</h3><ul><li><strong>时间复杂度 O(M+N)：</strong> M,N 分别为链表 l1,l2 的长度，合并操作需遍历两链表。</li><li><strong>空间复杂度 O(1) ：</strong> 节点引用 dum , cur 使用常数大小的额外空间。</li></ul><h2 id="52-两个链表的第一个公共节点"><a href="#52-两个链表的第一个公共节点" class="headerlink" title="52. 两个链表的第一个公共节点"></a>52. 两个链表的第一个公共节点</h2><h3 id="题目-29"><a href="#题目-29" class="headerlink" title="题目"></a>题目</h3><blockquote><p>输入两个链表，找出它们的第一个公共节点。</p><p>如下面的两个链表：</p><p><img src="https://img.jwt1399.top/img/202208201148217.png"></p><p>在节点 c1 开始相交。</p><p>示例 1：</p><p><img src="https://img.jwt1399.top/img/202208201148988.png"></p><pre><code>输入：intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3输出：Reference of the node with value = 8输入解释：相交节点的值为 8 （注意，如果两个列表相交则不能为 0）。从各自的表头开始算起，链表 A 为 [4,1,8,4,5]，链表 B 为 [5,0,1,8,4,5]。在 A 中，相交节点前有 2 个节点；在 B 中，相交节点前有 3 个节点。</code></pre><p>示例 2：</p><p><img src="https://img.jwt1399.top/img/202208201148108.png"></p><pre><code>输入：intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1输出：Reference of the node with value = 2输入解释：相交节点的值为 2 （注意，如果两个列表相交则不能为 0）。从各自的表头开始算起，链表 A 为 [0,9,1,2,4]，链表 B 为 [3,2,4]。在 A 中，相交节点前有 3 个节点；在 B 中，相交节点前有 1 个节点。</code></pre><p>示例 3：</p><p><img src="https://img.jwt1399.top/img/202208201148832.png"></p><pre><code>输入：intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2输出：null输入解释：从各自的表头开始算起，链表 A 为 [2,6,4]，链表 B 为 [1,5]。由于这两个链表不相交，所以 intersectVal 必须为 0，而 skipA 和 skipB 可以是任意值。解释：这两个链表不相交，因此返回 null。</code></pre></blockquote><h3 id="解答-29"><a href="#解答-29" class="headerlink" title="解答"></a>解答</h3><p>设「第一个公共节点」为 <code>node</code> ，「链表 <code>headA</code>」的节点数量为 a ，「链表 <code>headB</code>」的节点数量为 b ，「两链表的公共尾部」的节点数量为 c ，则有：</p><ul><li>头节点 <code>headA</code> 到 <code>node</code> 前，共有 a−c 个节点；</li><li>头节点 <code>headB</code> 到 <code>node</code> 前，共有 b−c 个节点；</li></ul><p><img src="https://img.jwt1399.top/img/202210261118942.png"></p><p>考虑构建两个节点指针 <code>A</code> , <code>B</code> 分别指向两链表头节点 <code>headA</code> , <code>headB</code> ，做如下操作：</p><ul><li><p>指针 <code>A</code> 先遍历完链表 <code>headA</code> ，再开始遍历链表 <code>headB</code> ，当走到 <code>node</code> 时，共走步数为：a+(b−c)</p></li><li><p>指针 <code>B</code> 先遍历完链表 <code>headB</code> ，再开始遍历链表 <code>headA</code> ，当走到 <code>node</code> 时，共走步数为：b+(a−c)</p></li></ul><p>a+(b−c) &#x3D; b+(a−c)，此时指针 <code>A</code> , <code>B</code> 重合 ，并有两种情况：</p><ol><li>若两链表 <strong>有</strong> 公共尾部 (即 c&gt;0 ) ：指针 <code>A</code> , <code>B</code> 同时指向「第一个公共节点」<code>node</code> 。</li><li>若两链表 <strong>无</strong> 公共尾部 (即 c=0) ：指针 <code>A</code> , <code>B</code> 同时指向 null 。</li></ol><p>因此返回 <code>A</code> 即可。</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">/** * Definition for singly-linked list. * public class ListNode { *     int val; *     ListNode next; *     ListNode(int x) { *         val = x; *         next = null; *     } * } */</span><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    ListNode <span class="token function">getIntersectionNode</span><span class="token punctuation">(</span>ListNode headA<span class="token punctuation">,</span> ListNode headB<span class="token punctuation">)</span> <span class="token punctuation">{</span>        ListNode A <span class="token operator">=</span> headA<span class="token punctuation">,</span> B <span class="token operator">=</span> headB<span class="token punctuation">;</span>        <span class="token keyword">while</span><span class="token punctuation">(</span>A <span class="token operator">!=</span> B<span class="token punctuation">)</span><span class="token punctuation">{</span>          <span class="token keyword">if</span><span class="token punctuation">(</span>A <span class="token operator">!=</span> null<span class="token punctuation">)</span> A <span class="token operator">=</span> A<span class="token punctuation">.</span>next<span class="token punctuation">;</span>          <span class="token keyword">else</span> A <span class="token operator">=</span> headB<span class="token punctuation">;</span>          <span class="token keyword">if</span><span class="token punctuation">(</span>B <span class="token operator">!=</span> null<span class="token punctuation">)</span> B <span class="token operator">=</span> B<span class="token punctuation">.</span>next<span class="token punctuation">;</span>          <span class="token keyword">else</span> B <span class="token operator">=</span> headA<span class="token punctuation">;</span>        <span class="token punctuation">}</span>         <span class="token keyword">return</span> A<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">//简化代码</span><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    ListNode <span class="token function">getIntersectionNode</span><span class="token punctuation">(</span>ListNode headA<span class="token punctuation">,</span> ListNode headB<span class="token punctuation">)</span> <span class="token punctuation">{</span>        ListNode A <span class="token operator">=</span> headA<span class="token punctuation">,</span> B <span class="token operator">=</span> headB<span class="token punctuation">;</span>        <span class="token keyword">while</span><span class="token punctuation">(</span>A <span class="token operator">!=</span> B<span class="token punctuation">)</span><span class="token punctuation">{</span>          A <span class="token operator">=</span> A <span class="token operator">!=</span> null <span class="token operator">?</span> A<span class="token punctuation">.</span>next <span class="token operator">:</span> headB<span class="token punctuation">;</span>          B <span class="token operator">=</span> B <span class="token operator">!=</span> null <span class="token operator">?</span> B<span class="token punctuation">.</span>next <span class="token operator">:</span> headA<span class="token punctuation">;</span>        <span class="token punctuation">}</span>         <span class="token keyword">return</span> A<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="复杂度-19"><a href="#复杂度-19" class="headerlink" title="复杂度"></a>复杂度</h3><ul><li><strong>时间复杂度 O(a+b)：</strong> 最差情况下（即 ∣a−b∣=1 , c=0 ），此时需遍历 a+b 个节点。</li><li><strong>空间复杂度 O(1)：</strong> 节点指针 <code>A</code> , <code>B</code> 使用常数大小的额外空间。</li></ul><h1 id="第-13-天-双指针（简单）"><a href="#第-13-天-双指针（简单）" class="headerlink" title="第 13 天 双指针（简单）"></a>第 13 天 双指针（简单）</h1><h2 id="21-调整数组顺序使奇数位于偶"><a href="#21-调整数组顺序使奇数位于偶" class="headerlink" title="21. 调整数组顺序使奇数位于偶"></a>21. 调整数组顺序使奇数位于偶</h2><h3 id="题目-30"><a href="#题目-30" class="headerlink" title="题目"></a>题目</h3><blockquote><p>输入一个整数数组，实现一个函数来调整该数组中数字的顺序，使得所有奇数在数组的前半部分，所有偶数在数组的后半部分。</p><p>示例：</p><pre><code>输入：nums = [1,2,3,4]输出：[1,3,2,4] 注：[3,1,2,4] 也是正确的答案之一。</code></pre></blockquote><h3 id="解答-30"><a href="#解答-30" class="headerlink" title="解答"></a>解答</h3><p><img src="https://img.jwt1399.top/img/202208201733132.png"></p><h5 id="算法流程：-3"><a href="#算法流程：-3" class="headerlink" title="算法流程："></a>算法流程：</h5><ol><li><strong>初始化：</strong> i , j 双指针，分别指向数组 nums 左右两端；</li><li><strong>循环交换：</strong> 当 i = j  时跳出；<ol><li>指针 i 遇到奇数则执行 i = i+1 跳过，直到找到偶数；</li><li>指针 j 遇到偶数则执行 j = j−1 跳过，直到找到奇数；</li><li>交换 nums[i] 和 nums[j] 值；</li></ol></li><li><strong>返回值：</strong> 返回已修改的 nums 数组。</li></ol><p> 位运算判断奇偶性</p><p>  “**<code>1</code>**”的二进制 表示形式为 <strong><code>00000001</code></strong><br><strong>所以：</strong><code>任何整数 &amp; 1</code></p><ul><li>结果为 1 ，则为奇数</li><li>结果为 0 ，则为偶数</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token function">exchange</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> nums<span class="token punctuation">)</span> <span class="token punctuation">{</span>      <span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">,</span> j <span class="token operator">=</span> nums<span class="token punctuation">.</span>length <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">,</span> tmp<span class="token punctuation">;</span>      <span class="token keyword">while</span><span class="token punctuation">(</span>i <span class="token operator">&lt;</span> j<span class="token punctuation">)</span><span class="token punctuation">{</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>nums<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">%</span> <span class="token number">2</span> <span class="token operator">!=</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">{</span>           i<span class="token operator">++</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>nums<span class="token punctuation">[</span>j<span class="token punctuation">]</span> <span class="token operator">%</span> <span class="token number">2</span> <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">{</span>            j<span class="token operator">--</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>i <span class="token operator">&lt;</span> j<span class="token punctuation">)</span><span class="token punctuation">{</span>          tmp <span class="token operator">=</span> nums<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">;</span>          nums<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> nums<span class="token punctuation">[</span>j<span class="token punctuation">]</span><span class="token punctuation">;</span>          nums<span class="token punctuation">[</span>j<span class="token punctuation">]</span> <span class="token operator">=</span> tmp<span class="token punctuation">;</span>         <span class="token punctuation">}</span>      <span class="token punctuation">}</span>      <span class="token keyword">return</span> nums<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">//优化</span><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token function">exchange</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> nums<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">,</span> j <span class="token operator">=</span> nums<span class="token punctuation">.</span>length <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">,</span> tmp<span class="token punctuation">;</span>        <span class="token keyword">while</span><span class="token punctuation">(</span>i <span class="token operator">&lt;</span> j<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token keyword">while</span><span class="token punctuation">(</span>i <span class="token operator">&lt;</span> j <span class="token operator">&amp;&amp;</span> <span class="token punctuation">(</span>nums<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">&amp;</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token number">1</span><span class="token punctuation">)</span> i<span class="token operator">++</span><span class="token punctuation">;</span>            <span class="token keyword">while</span><span class="token punctuation">(</span>i <span class="token operator">&lt;</span> j <span class="token operator">&amp;&amp;</span> <span class="token punctuation">(</span>nums<span class="token punctuation">[</span>j<span class="token punctuation">]</span> <span class="token operator">&amp;</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> j<span class="token operator">--</span><span class="token punctuation">;</span>            tmp <span class="token operator">=</span> nums<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">;</span>            nums<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> nums<span class="token punctuation">[</span>j<span class="token punctuation">]</span><span class="token punctuation">;</span>            nums<span class="token punctuation">[</span>j<span class="token punctuation">]</span> <span class="token operator">=</span> tmp<span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> nums<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="复杂度-20"><a href="#复杂度-20" class="headerlink" title="复杂度"></a>复杂度</h3><ul><li><strong>时间复杂度 O(N) ：</strong> NNN 为数组 nums 长度，双指针 i, j 共同遍历整个数组。</li><li><strong>空间复杂度 O(1) ：</strong> 双指针 i, j 使用常数大小的额外空间。</li></ul><h2 id="57-和为s的两个数字"><a href="#57-和为s的两个数字" class="headerlink" title="57. 和为s的两个数字"></a>57. 和为s的两个数字</h2><h3 id="题目-31"><a href="#题目-31" class="headerlink" title="题目"></a>题目</h3><blockquote><p>输入一个递增排序的数组和一个数字s，在数组中查找两个数，使得它们的和正好是s。如果有多对数字的和等于s，则输出任意一对即可。</p><p>示例 1：</p><pre><code>输入：nums = [2,7,11,15], target = 9输出：[2,7] 或者 [7,2]</code></pre><p>示例 2：</p><pre><code>输入：nums = [10,26,30,31,47,60], target = 40输出：[10,30] 或者 [30,10]</code></pre></blockquote><h3 id="解答-31"><a href="#解答-31" class="headerlink" title="解答"></a>解答</h3><h5 id="算法流程：-4"><a href="#算法流程：-4" class="headerlink" title="算法流程："></a>算法流程：</h5><ol><li><strong>初始化：</strong> 双指针 i , j 分别指向数组 nums 的左右两端 （俗称对撞双指针）。</li><li><strong>循环搜索：</strong> 当双指针相遇时跳出；<ol><li>计算和 sum = nums[i]+nums[j]；</li><li>若 sum &gt; target，则指针 j 向左移动，即执行 j=j−1 ；</li><li>若 sum &lt; target ，则指针 i 向右移动，即执行 i=i+1 ；</li><li>若 sum = target ，立即返回数组 [nums[i],nums[j]] ；</li></ol></li><li>返回空数组，代表无和为 target 的数字组合。</li></ol><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token function">twoSum</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> nums<span class="token punctuation">,</span> <span class="token keyword">int</span> target<span class="token punctuation">)</span> <span class="token punctuation">{</span>      <span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">,</span> j <span class="token operator">=</span> nums<span class="token punctuation">.</span>length <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span>      <span class="token keyword">while</span><span class="token punctuation">(</span>i <span class="token operator">&lt;</span> j<span class="token punctuation">)</span><span class="token punctuation">{</span>        <span class="token keyword">int</span> sum <span class="token operator">=</span> nums<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">+</span> nums<span class="token punctuation">[</span>j<span class="token punctuation">]</span><span class="token punctuation">;</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>sum <span class="token operator">></span> target<span class="token punctuation">)</span> j<span class="token operator">--</span><span class="token punctuation">;</span>        <span class="token keyword">else</span> <span class="token keyword">if</span><span class="token punctuation">(</span>sum <span class="token operator">&lt;</span> target<span class="token punctuation">)</span> i<span class="token operator">++</span><span class="token punctuation">;</span>        <span class="token keyword">else</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">{</span>nums<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">,</span>nums<span class="token punctuation">[</span>j<span class="token punctuation">]</span><span class="token punctuation">}</span><span class="token punctuation">;</span>      <span class="token punctuation">}</span>      <span class="token keyword">return</span> null<span class="token punctuation">;</span>      <span class="token comment" spellcheck="true">//return new int[0];</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="复杂度-21"><a href="#复杂度-21" class="headerlink" title="复杂度"></a>复杂度</h3><ul><li><strong>时间复杂度 O(N) ：</strong> N 为数组 nums 的长度；双指针共同线性遍历整个数组。</li><li><strong>空间复杂度 O(1) ：</strong> 变量 i, j 使用常数大小的额外空间。</li></ul><h2 id="58-I-翻转单词顺序"><a href="#58-I-翻转单词顺序" class="headerlink" title="58 - I. 翻转单词顺序"></a>58 - I. 翻转单词顺序</h2><h3 id="题目-32"><a href="#题目-32" class="headerlink" title="题目"></a>题目</h3><blockquote><p>输入一个英文句子，翻转句子中单词的顺序，但单词内字符的顺序不变。为简单起见，标点符号和普通字母一样处理。例如输入字符串”I am a student. “，则输出”student. a am I”。</p><p>示例 1：</p><pre><code>输入: &quot;the sky is blue&quot;输出: &quot;blue is sky the&quot;</code></pre><p>示例 2：</p><pre><code>输入: &quot;  hello world!  &quot;输出: &quot;world! hello&quot;解释: 输入字符串可以在前面或者后面包含多余的空格，但是反转后的字符不能包括。</code></pre><p>示例 3：</p><pre><code>输入: &quot;a good   example&quot;输出: &quot;example good a&quot;解释: 如果两个单词间有多余的空格，将反转后单词间的空格减少到只含一个。</code></pre></blockquote><h3 id="解答-32"><a href="#解答-32" class="headerlink" title="解答"></a>解答</h3><h4 id="方法1：调用API"><a href="#方法1：调用API" class="headerlink" title="方法1：调用API"></a>方法1：调用API</h4><ol><li>使用 <code>split</code> 将字符串按空格分割成字符串数组；</li><li>使用 <code>reverse</code> 将字符串数组进行反转；</li><li>使用 <code>join</code> 方法将字符串数组拼成一个字符串。</li></ol><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> String <span class="token function">reverseWords</span><span class="token punctuation">(</span>String s<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 除去开头和末尾的空白字符</span>        s <span class="token operator">=</span> s<span class="token punctuation">.</span><span class="token function">trim</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 正则匹配连续的空白字符作为分隔符分割</span>        List<span class="token operator">&lt;</span>String<span class="token operator">></span> wordList <span class="token operator">=</span> Arrays<span class="token punctuation">.</span><span class="token function">asList</span><span class="token punctuation">(</span>s<span class="token punctuation">.</span><span class="token function">split</span><span class="token punctuation">(</span><span class="token string">"\\s+"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        Collections<span class="token punctuation">.</span><span class="token function">reverse</span><span class="token punctuation">(</span>wordList<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> String<span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span><span class="token string">" "</span><span class="token punctuation">,</span> wordList<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li><code>Arrays.asList</code> 将数组转化成List集合</li><li><code>Collections.reverse </code>反转列表元素</li><li><code>String.join</code> 用给定的定界符连接数组或列表中所有元素</li><li>“<code>\s+</code>”则表示匹配任意多个空白字符。另因为反斜杠在Java里是转义字符，所以在Java里，我们要这么用“<code>\\s+</code>”</li></ul><h4 id="方法2：双指针"><a href="#方法2：双指针" class="headerlink" title="方法2：双指针"></a>方法2：<strong>双指针</strong></h4><h5 id="算法解析："><a href="#算法解析：" class="headerlink" title="算法解析："></a>算法解析：</h5><ul><li>倒序遍历字符串 s ，记录单词左右索引边界 i , j ；</li><li>每确定一个单词的边界，则将其添加至单词列表 res ；</li><li>最终，将单词列表拼接为字符串，并返回即可。</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> String <span class="token function">reverseWords</span><span class="token punctuation">(</span>String s<span class="token punctuation">)</span> <span class="token punctuation">{</span>        s <span class="token operator">=</span> s<span class="token punctuation">.</span><span class="token function">trim</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 删除首尾空格</span>        <span class="token keyword">int</span> j <span class="token operator">=</span> s<span class="token punctuation">.</span><span class="token function">length</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">,</span> i <span class="token operator">=</span> j<span class="token punctuation">;</span>        StringBuilder res <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">StringBuilder</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">while</span><span class="token punctuation">(</span>i <span class="token operator">>=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token keyword">while</span><span class="token punctuation">(</span>i <span class="token operator">>=</span> <span class="token number">0</span> <span class="token operator">&amp;&amp;</span> s<span class="token punctuation">.</span><span class="token function">charAt</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span> <span class="token operator">!=</span> <span class="token string">' '</span><span class="token punctuation">)</span> i<span class="token operator">--</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 搜索首个空格</span>            res<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span>s<span class="token punctuation">.</span><span class="token function">substring</span><span class="token punctuation">(</span>i <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">,</span> j <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">" "</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 添加单词</span>            <span class="token keyword">while</span><span class="token punctuation">(</span>i <span class="token operator">>=</span> <span class="token number">0</span> <span class="token operator">&amp;&amp;</span> s<span class="token punctuation">.</span><span class="token function">charAt</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token string">' '</span><span class="token punctuation">)</span> i<span class="token operator">--</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 跳过单词间空格</span>            j <span class="token operator">=</span> i<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// j 指向下个单词的尾字符</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> res<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">trim</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 转化为字符串，删除尾部空格，并返回</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="复杂度-22"><a href="#复杂度-22" class="headerlink" title="复杂度"></a>复杂度</h3><h4 id="方法1-5"><a href="#方法1-5" class="headerlink" title="方法1"></a>方法1</h4><ul><li><p>**时间复杂度O(N)**：其中 N 为字符串 s 的长度。</p></li><li><p>**空间复杂度O(N)**：用来存储字符串分割之后的结果。</p></li></ul><h4 id="方法2-5"><a href="#方法2-5" class="headerlink" title="方法2"></a>方法2</h4><ul><li><strong>时间复杂度 O(N)：</strong> 其中 N 为字符串 s 的长度，线性遍历字符串。</li><li><strong>空间复杂度 O(N) ：</strong> 新建的 StringBuilder(Java) 中的字符串总长度 ≤N，占用 O(N) 大小的额外空间。</li></ul><h1 id="第-14-天-搜索与回溯算法（中等）"><a href="#第-14-天-搜索与回溯算法（中等）" class="headerlink" title="第 14 天 搜索与回溯算法（中等）"></a>第 14 天 搜索与回溯算法（中等）</h1><h2 id="12-矩阵中的路径"><a href="#12-矩阵中的路径" class="headerlink" title="12. 矩阵中的路径"></a>12. 矩阵中的路径</h2><h3 id="题目-33"><a href="#题目-33" class="headerlink" title="题目"></a>题目</h3><blockquote><p>给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中，返回 true ；否则，返回 false 。</p><p>单词必须按照字母顺序，通过相邻的单元格内的字母构成，其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。</p><p>例如，在下面的 3×4 的矩阵中包含单词 “ABCCED”（单词中的字母已标出）。</p><p><img src="https://img.jwt1399.top/img/202208211450131.jpg"></p><p>示例 1：</p><pre><code>输入：board = [[&quot;A&quot;,&quot;B&quot;,&quot;C&quot;,&quot;E&quot;],[&quot;S&quot;,&quot;F&quot;,&quot;C&quot;,&quot;S&quot;],[&quot;A&quot;,&quot;D&quot;,&quot;E&quot;,&quot;E&quot;]], word = &quot;ABCCED&quot;输出：true</code></pre><p>示例 2：</p><pre><code>输入：board = [[&quot;a&quot;,&quot;b&quot;],[&quot;c&quot;,&quot;d&quot;]], word = &quot;abcd&quot;输出：false</code></pre></blockquote><h3 id="解答-33"><a href="#解答-33" class="headerlink" title="解答"></a>解答</h3><p><img src="https://img.jwt1399.top/img/202208301121232.png"></p><h5 id="DFS-解析："><a href="#DFS-解析：" class="headerlink" title="DFS 解析："></a>DFS 解析：</h5><ul><li><strong>递归参数：</strong> 当前元素在矩阵 <code>board</code> 中的行列索引 <code>i</code> 和 <code>j</code> ，当前目标字符在 <code>word</code> 中的索引 <code>k</code> </li><li><strong>终止条件：</strong><ol><li>返回 false ： (1) 行或列索引越界 <strong>或</strong> (2) 当前矩阵元素与目标字符不同 <strong>或</strong> (3) 当前矩阵元素已访问过 （ (3) 可合并至 (2) ） 。</li><li>返回 true ： <code>k = len(word) - 1</code> ，即字符串 <code>word</code> 已全部匹配。</li></ol></li><li><strong>递推工作：</strong><ol><li>标记当前矩阵元素： 将 <code>board[i][j]</code> 修改为 <strong>空字符</strong> <code>&#39;&#39;</code> ，代表此元素已访问过，防止之后搜索时重复访问。</li><li>搜索下一单元格： 朝当前元素的 <strong>上、下、左、右</strong> 四个方向开启下层递归，使用 <code>或</code> 连接 （代表只需找到一条可行路径就直接返回，不再做后续 DFS ），并记录结果至 <code>res</code> 。</li><li>还原当前矩阵元素： 将 <code>board[i][j]</code> 元素还原至初始值，即 <code>word[k]</code> 。</li></ol></li><li><strong>返回值：</strong> 返回布尔量 <code>res</code> ，代表是否搜索到目标字符串</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">boolean</span> <span class="token function">exist</span><span class="token punctuation">(</span><span class="token keyword">char</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token punctuation">]</span> board<span class="token punctuation">,</span> String word<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">char</span><span class="token punctuation">[</span><span class="token punctuation">]</span> words <span class="token operator">=</span> word<span class="token punctuation">.</span><span class="token function">toCharArray</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> board<span class="token punctuation">.</span>length<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> j <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> j <span class="token operator">&lt;</span> board<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span>length<span class="token punctuation">;</span> j<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token keyword">if</span><span class="token punctuation">(</span><span class="token function">dfs</span><span class="token punctuation">(</span>board<span class="token punctuation">,</span> words<span class="token punctuation">,</span> i<span class="token punctuation">,</span> j<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">boolean</span> <span class="token function">dfs</span><span class="token punctuation">(</span><span class="token keyword">char</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token punctuation">]</span> board<span class="token punctuation">,</span> <span class="token keyword">char</span><span class="token punctuation">[</span><span class="token punctuation">]</span> word<span class="token punctuation">,</span> <span class="token keyword">int</span> i<span class="token punctuation">,</span> <span class="token keyword">int</span> j<span class="token punctuation">,</span> <span class="token keyword">int</span> k<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>i <span class="token operator">>=</span> board<span class="token punctuation">.</span>length <span class="token operator">||</span> i <span class="token operator">&lt;</span> <span class="token number">0</span> <span class="token operator">||</span> j <span class="token operator">>=</span> board<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span>length <span class="token operator">||</span> j <span class="token operator">&lt;</span> <span class="token number">0</span> <span class="token operator">||</span> board<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span>j<span class="token punctuation">]</span> <span class="token operator">!=</span> word<span class="token punctuation">[</span>k<span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>k <span class="token operator">==</span> word<span class="token punctuation">.</span>length <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>        board<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span>j<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token string">'\0'</span><span class="token punctuation">;</span>        <span class="token keyword">boolean</span> res <span class="token operator">=</span> <span class="token function">dfs</span><span class="token punctuation">(</span>board<span class="token punctuation">,</span> word<span class="token punctuation">,</span> i <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">,</span> j<span class="token punctuation">,</span> k <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">||</span> <span class="token function">dfs</span><span class="token punctuation">(</span>board<span class="token punctuation">,</span> word<span class="token punctuation">,</span> i <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">,</span> j<span class="token punctuation">,</span> k <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">||</span>                       <span class="token function">dfs</span><span class="token punctuation">(</span>board<span class="token punctuation">,</span> word<span class="token punctuation">,</span> i<span class="token punctuation">,</span> j <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">,</span> k <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">||</span> <span class="token function">dfs</span><span class="token punctuation">(</span>board<span class="token punctuation">,</span> word<span class="token punctuation">,</span> i <span class="token punctuation">,</span> j <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">,</span> k <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        board<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span>j<span class="token punctuation">]</span> <span class="token operator">=</span> word<span class="token punctuation">[</span>k<span class="token punctuation">]</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> res<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="复杂度-23"><a href="#复杂度-23" class="headerlink" title="复杂度"></a>复杂度</h3><blockquote><p>M,N 分别为矩阵行列大小， K 为字符串 <code>word</code> 长度。</p></blockquote><ul><li><strong>时间复杂度 O(3<sup>K</sup>MN)：</strong> 最差情况下，需要遍历矩阵中长度为 K 字符串的所有方案，时间复杂度为 O(3<sup>K</sup>)；矩阵中共有 MN 个起点，时间复杂度为 O(MN) 。<ul><li><strong>方案数计算：</strong> 设字符串长度为 K ，搜索中每个字符有上、下、左、右四个方向可以选择，舍弃回头（上个字符）的方向，剩下 3 种选择，因此方案数的复杂度为 O(3<sup>K</sup>) 。</li></ul></li><li><strong>空间复杂度 O(K) ：</strong> 搜索过程中的递归深度不超过 K ，因此系统因函数调用累计使用的栈空间占用 O(K) （因为函数返回后，系统调用的栈空间会释放）。最坏情况下 K=MN，递归深度为 MN ，此时系统栈使用 O(MN) 的额外空间。</li></ul><h2 id="13-机器人的运动范围"><a href="#13-机器人的运动范围" class="headerlink" title="13. 机器人的运动范围"></a>13. 机器人的运动范围</h2><h3 id="题目-34"><a href="#题目-34" class="headerlink" title="题目"></a>题目</h3><blockquote><p>地上有一个m行n列的方格，从坐标 [0,0] 到坐标 [m-1,n-1] 。一个机器人从坐标 [0, 0] 的格子开始移动，它每次可以向左、右、上、下移动一格（不能移动到方格外），也不能进入行坐标和列坐标的数位之和大于k的格子。例如，当k为18时，机器人能够进入方格 [35, 37] ，因为3+5+3+7&#x3D;18。但它不能进入方格 [35, 38]，因为3+5+3+8&#x3D;19。请问该机器人能够到达多少个格子？</p><p>示例 1：</p><pre><code>输入：m = 2, n = 3, k = 1输出：3</code></pre><p>示例 2：</p><pre><code>输入：m = 3, n = 1, k = 0输出：1</code></pre></blockquote><h3 id="解答-34"><a href="#解答-34" class="headerlink" title="解答"></a>解答</h3><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">movingCount</span><span class="token punctuation">(</span><span class="token keyword">int</span> m<span class="token punctuation">,</span> <span class="token keyword">int</span> n<span class="token punctuation">,</span> <span class="token keyword">int</span> k<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 记录位置是否被遍历过</span>        <span class="token keyword">boolean</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token punctuation">]</span> visited <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">boolean</span><span class="token punctuation">[</span>m<span class="token punctuation">]</span><span class="token punctuation">[</span>n<span class="token punctuation">]</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> <span class="token function">dfs</span><span class="token punctuation">(</span>visited<span class="token punctuation">,</span> m<span class="token punctuation">,</span> n<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> k<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">private</span> <span class="token keyword">int</span> <span class="token function">dfs</span><span class="token punctuation">(</span><span class="token keyword">boolean</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token punctuation">]</span> visited<span class="token punctuation">,</span> <span class="token keyword">int</span> m<span class="token punctuation">,</span> <span class="token keyword">int</span> n<span class="token punctuation">,</span> <span class="token keyword">int</span> i<span class="token punctuation">,</span> <span class="token keyword">int</span> j<span class="token punctuation">,</span> <span class="token keyword">int</span> k<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// i >= m || j >= n是边界条件的判断</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>i <span class="token operator">>=</span> m <span class="token operator">||</span> j <span class="token operator">>=</span> n                <span class="token comment" spellcheck="true">// visited[i][j]判断这个格子是否被访问过</span>                <span class="token operator">||</span> visited<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span>j<span class="token punctuation">]</span> <span class="token operator">==</span> <span class="token boolean">true</span>                <span class="token comment" spellcheck="true">// 判断当前格子坐标是否满足条件</span>                <span class="token operator">||</span> <span class="token function">bitSum</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token function">bitSum</span><span class="token punctuation">(</span>j<span class="token punctuation">)</span> <span class="token operator">></span> k<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 标注这个格子被访问过</span>        visited<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span>j<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 沿着当前格子的右边和下边继续访问</span>        <span class="token comment" spellcheck="true">//代表从本单元格递归搜索的可达解总数。</span>        <span class="token keyword">return</span> <span class="token number">1</span> <span class="token operator">+</span> <span class="token function">dfs</span><span class="token punctuation">(</span>visited<span class="token punctuation">,</span> m<span class="token punctuation">,</span> n<span class="token punctuation">,</span> i <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">,</span> j<span class="token punctuation">,</span> k<span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token function">dfs</span><span class="token punctuation">(</span>visited<span class="token punctuation">,</span> m<span class="token punctuation">,</span> n<span class="token punctuation">,</span> i<span class="token punctuation">,</span> j <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">,</span> k<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">// 计算数位和</span>    <span class="token keyword">private</span> <span class="token keyword">int</span> <span class="token function">bitSum</span><span class="token punctuation">(</span><span class="token keyword">int</span> x<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">int</span> sum <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>        <span class="token keyword">while</span><span class="token punctuation">(</span>x <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            sum <span class="token operator">+=</span> x <span class="token operator">%</span> <span class="token number">10</span><span class="token punctuation">;</span>            x <span class="token operator">/=</span> <span class="token number">10</span><span class="token punctuation">;</span>         <span class="token punctuation">}</span>        <span class="token keyword">return</span> sum<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="复杂度-24"><a href="#复杂度-24" class="headerlink" title="复杂度"></a>复杂度</h3><ul><li><strong>时间复杂度 O(MN) ：</strong> 最差情况下，机器人遍历矩阵所有单元格，此时时间复杂度为 O(MN) 。</li><li><strong>空间复杂度 O(MN)：</strong> 最差情况下， <code>visited</code> 内存储矩阵所有单元格的索引，使用 O(MN) 的额外空间。</li></ul><h1 id="第-15-天-搜索与回溯算法（中等）"><a href="#第-15-天-搜索与回溯算法（中等）" class="headerlink" title="第 15 天 搜索与回溯算法（中等）"></a>第 15 天 搜索与回溯算法（中等）</h1><h2 id="34-二叉树中和为某一值的路径"><a href="#34-二叉树中和为某一值的路径" class="headerlink" title="34. 二叉树中和为某一值的路径"></a>34. 二叉树中和为某一值的路径</h2><h3 id="题目-35"><a href="#题目-35" class="headerlink" title="题目"></a>题目</h3><blockquote><p>给你二叉树的根节点 root 和一个整数目标和 targetSum ，找出所有 从根节点到叶子节点路径总和等于给定目标和的路径。</p><p>叶子节点 是指没有子节点的节点。</p><p>示例 1：</p><p><img src="https://img.jwt1399.top/img/202208232302265.jpg"></p><pre><code>输入：root = [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum = 22输出：[[5,4,11,2],[5,8,4,5]]</code></pre><p>示例 2：</p><p><img src="https://img.jwt1399.top/img/202208232302516.jpg"></p><pre><code>输入：root = [1,2,3], targetSum = 5输出：[]</code></pre><p>示例 3：</p><pre><code>输入：root = [1,2], targetSum = 0输出：[]</code></pre></blockquote><h3 id="解答-35"><a href="#解答-35" class="headerlink" title="解答"></a>解答</h3><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">/** * Definition for a binary tree node. * public class TreeNode { *     int val; *     TreeNode left; *     TreeNode right; *     TreeNode() {} *     TreeNode(int val) { this.val = val; } *     TreeNode(int val, TreeNode left, TreeNode right) { *         this.val = val; *         this.left = left; *         this.right = right; *     } * } */</span><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    LinkedList<span class="token operator">&lt;</span>Integer<span class="token operator">></span> path <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">LinkedList</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      LinkedList<span class="token operator">&lt;</span>List<span class="token operator">&lt;</span>Integer<span class="token operator">>></span> res <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">LinkedList</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">public</span> List<span class="token operator">&lt;</span>List<span class="token operator">&lt;</span>Integer<span class="token operator">>></span> <span class="token function">pathSum</span><span class="token punctuation">(</span>TreeNode root<span class="token punctuation">,</span> <span class="token keyword">int</span> target<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token function">dfs</span><span class="token punctuation">(</span>root<span class="token punctuation">,</span> target<span class="token punctuation">)</span><span class="token punctuation">;</span>          <span class="token keyword">return</span> res<span class="token punctuation">;</span>          <span class="token punctuation">}</span>    <span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">dfs</span><span class="token punctuation">(</span>TreeNode root<span class="token punctuation">,</span> <span class="token keyword">int</span> target<span class="token punctuation">)</span><span class="token punctuation">{</span>      <span class="token keyword">if</span><span class="token punctuation">(</span>root <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token keyword">return</span><span class="token punctuation">;</span>      path<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>root<span class="token punctuation">.</span>val<span class="token punctuation">)</span><span class="token punctuation">;</span>      target <span class="token operator">-=</span> root<span class="token punctuation">.</span>val<span class="token punctuation">;</span>      <span class="token keyword">if</span><span class="token punctuation">(</span>target <span class="token operator">==</span> <span class="token number">0</span> <span class="token operator">&amp;&amp;</span> root<span class="token punctuation">.</span>left <span class="token operator">==</span> null <span class="token operator">&amp;&amp;</span> root<span class="token punctuation">.</span>right <span class="token operator">==</span> null<span class="token punctuation">)</span>             <span class="token comment" spellcheck="true">// 细节：为什么我要通过构造方法传入path，不能直接res.add(path)</span>            <span class="token comment" spellcheck="true">//因为直接加入，加入的是引用(指向的堆中数据会变化)，需要克隆一份加入</span>         res<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">LinkedList</span><span class="token punctuation">(</span>path<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token function">dfs</span><span class="token punctuation">(</span>root<span class="token punctuation">.</span>left<span class="token punctuation">,</span> target<span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token function">dfs</span><span class="token punctuation">(</span>root<span class="token punctuation">.</span>right<span class="token punctuation">,</span> target<span class="token punctuation">)</span><span class="token punctuation">;</span>      path<span class="token punctuation">.</span><span class="token function">removeLast</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 将本次搜索结果移除，方便其他搜索使用path变量</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="复杂度-25"><a href="#复杂度-25" class="headerlink" title="复杂度"></a>复杂度</h3><ul><li><strong>时间复杂度 O(N) ：</strong> N 为二叉树的节点数，先序遍历需要遍历所有节点。</li><li><strong>空间复杂度 O(N) ：</strong> 最差情况下，即树退化为链表时，<code>path</code> 存储所有树节点，使用 O(N) 额外空间。</li></ul><h2 id="36-二叉搜索树与双向链表"><a href="#36-二叉搜索树与双向链表" class="headerlink" title="36. 二叉搜索树与双向链表"></a>36. 二叉搜索树与双向链表</h2><h3 id="题目-36"><a href="#题目-36" class="headerlink" title="题目"></a>题目</h3><blockquote><p>输入一棵二叉搜索树，将该二叉搜索树转换成一个排序的循环双向链表。要求不能创建任何新的节点，只能调整树中节点指针的指向。</p><p>为了让您更好地理解问题，以下面的二叉搜索树为例：</p><p><img src="https://img.jwt1399.top/img/202208240010290.png"></p><p>我们希望将这个二叉搜索树转化为双向循环链表。链表中的每个节点都有一个前驱和后继指针。对于双向循环链表，第一个节点的前驱是最后一个节点，最后一个节点的后继是第一个节点。</p><p>下图展示了上面的二叉搜索树转化成的链表。“head” 表示指向链表中有最小元素的节点。</p><p><img src="https://img.jwt1399.top/img/202208240010148.png"></p><p>特别地，我们希望可以就地完成转换操作。当转化完成以后，树中节点的左指针需要指向前驱，树中节点的右指针需要指向后继。还需要返回链表中的第一个节点的指针。</p></blockquote><h3 id="解答-36"><a href="#解答-36" class="headerlink" title="解答"></a>解答</h3><p>将<strong>二叉搜索树</strong>转换成一个 “<strong>排序的循环双向链表</strong>” ，其中包含三个要素：</p><ol><li><strong>排序链表：</strong> 二叉搜索树的中序遍历为 <strong>递增序列</strong> 。</li><li><strong>双向链表：</strong> 在构建相邻节点的引用关系时，设前驱节点 <code>pre</code> 和当前节点 <code>cur</code> ，不仅应构建 <code>pre.right = cur</code> ，也应构建 <code>cur.left = pre</code> 。</li><li><strong>循环链表：</strong> 设链表头节点 <code>head</code> 和尾节点 <code>tail</code> ，则应构建 <code>head.left = tail</code> 和 <code>tail.right = head</code> 。</li></ol><p><img src="https://img.jwt1399.top/img/202208241646204.png"></p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    Node pre<span class="token punctuation">,</span> head<span class="token punctuation">;</span>    <span class="token keyword">public</span> Node <span class="token function">treeToDoublyList</span><span class="token punctuation">(</span>Node root<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>root <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token keyword">return</span> null<span class="token punctuation">;</span>        <span class="token function">dfs</span><span class="token punctuation">(</span>root<span class="token punctuation">)</span><span class="token punctuation">;</span>        head<span class="token punctuation">.</span>left <span class="token operator">=</span> pre<span class="token punctuation">;</span>        pre<span class="token punctuation">.</span>right <span class="token operator">=</span> head<span class="token punctuation">;</span>        <span class="token keyword">return</span> head<span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">void</span> <span class="token function">dfs</span><span class="token punctuation">(</span>Node cur<span class="token punctuation">)</span><span class="token punctuation">{</span>      <span class="token keyword">if</span><span class="token punctuation">(</span>cur <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token keyword">return</span><span class="token punctuation">;</span>      <span class="token function">dfs</span><span class="token punctuation">(</span>cur<span class="token punctuation">.</span>left<span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token keyword">if</span><span class="token punctuation">(</span>pre <span class="token operator">!=</span> null<span class="token punctuation">)</span>        pre<span class="token punctuation">.</span>right <span class="token operator">=</span> cur<span class="token punctuation">;</span>      <span class="token keyword">else</span>        head <span class="token operator">=</span> cur<span class="token punctuation">;</span>      cur<span class="token punctuation">.</span>left <span class="token operator">=</span> pre<span class="token punctuation">;</span>      pre <span class="token operator">=</span> cur<span class="token punctuation">;</span>      <span class="token function">dfs</span><span class="token punctuation">(</span>cur<span class="token punctuation">.</span>right<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="复杂度-26"><a href="#复杂度-26" class="headerlink" title="复杂度"></a>复杂度</h3><ul><li><strong>时间复杂度 O(N) ：</strong> N 为二叉树的节点数，中序遍历需要访问所有节点。</li><li><strong>空间复杂度 O(N) ：</strong> 最差情况下，即树退化为链表时，递归深度达到 N，系统使用 O(N) 栈空间。</li></ul><h2 id="54-二叉搜索树的第k大节点"><a href="#54-二叉搜索树的第k大节点" class="headerlink" title="54.二叉搜索树的第k大节点"></a>54.二叉搜索树的第k大节点</h2><h3 id="题目-37"><a href="#题目-37" class="headerlink" title="题目"></a>题目</h3><blockquote><p>给定一棵二叉搜索树，请找出其中第 k 大的节点的值。</p><p>示例 1:</p><pre><code>输入: root = [3,1,4,null,2], k = 1  3 / \1   4 \  2输出: 4</code></pre><p>示例 2:</p><pre><code>输入: root = [5,3,6,2,4,null,null,1], k = 3      5     / \    3   6   / \  2   4 /1输出: 4</code></pre></blockquote><h3 id="解答-37"><a href="#解答-37" class="headerlink" title="解答"></a>解答</h3><p><img src="https://img.jwt1399.top/img/202208241746296.png"></p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">int</span> count<span class="token operator">=</span><span class="token number">0</span><span class="token punctuation">,</span> res<span class="token operator">=</span><span class="token number">0</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//形参k不能随着dfs的迭代而不断变化，为了记录迭代进程和结果，引入类变量count和res。</span>    <span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">kthLargest</span><span class="token punctuation">(</span>TreeNode root<span class="token punctuation">,</span> <span class="token keyword">int</span> k<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">this</span><span class="token punctuation">.</span>count<span class="token operator">=</span>k<span class="token punctuation">;</span><span class="token comment" spellcheck="true">//利用形参值k对类变量count进行初始化</span>        <span class="token function">dfs</span><span class="token punctuation">(</span>root<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//这里不要引入形参k，dfs中直接使用的是初始值为k的类变量count</span>        <span class="token keyword">return</span> res<span class="token punctuation">;</span>                <span class="token punctuation">}</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">dfs</span><span class="token punctuation">(</span>TreeNode root<span class="token punctuation">)</span><span class="token punctuation">{</span> <span class="token comment" spellcheck="true">//中序遍历的倒序</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>root<span class="token operator">==</span>null<span class="token operator">||</span>count<span class="token operator">==</span><span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">return</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//当root为空或者已经找到了res时，直接返回</span>        <span class="token function">dfs</span><span class="token punctuation">(</span>root<span class="token punctuation">.</span>right<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">if</span><span class="token punctuation">(</span><span class="token operator">--</span>count<span class="token operator">==</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">{</span><span class="token comment" spellcheck="true">//先--，再判断</span>            res <span class="token operator">=</span> root<span class="token punctuation">.</span>val<span class="token punctuation">;</span>            <span class="token keyword">return</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//这里的return可以避免之后的无效迭代dfs(root.left);</span>        <span class="token punctuation">}</span>        <span class="token function">dfs</span><span class="token punctuation">(</span>root<span class="token punctuation">.</span>left<span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="复杂度-27"><a href="#复杂度-27" class="headerlink" title="复杂度"></a>复杂度</h3><ul><li><strong>时间复杂度 O(N)：</strong> 当树退化为链表时（全部为右子节点），无论 k 的值大小，递归深度都为 N ，占用 O(N) 时间。</li><li><strong>空间复杂度 O(N)：</strong> 当树退化为链表时（全部为右子节点），系统使用 O(N) 大小的栈空间。</li></ul><h1 id="第-16-天-排序（简单）"><a href="#第-16-天-排序（简单）" class="headerlink" title="第 16 天 排序（简单）"></a>第 16 天 排序（简单）</h1><h2 id="45-把数组排成最小的数"><a href="#45-把数组排成最小的数" class="headerlink" title="45.把数组排成最小的数"></a>45.把数组排成最小的数</h2><h3 id="题目-38"><a href="#题目-38" class="headerlink" title="题目"></a>题目</h3><blockquote><p>输入一个非负整数数组，把数组里所有数字拼接起来排成一个数，打印能拼接出的所有数字中最小的一个。</p><p>示例 1:</p><pre><code>输入: [10,2]输出: &quot;102&quot;</code></pre><p>示例 2:</p><pre><code>输入: [3,30,34,5,9]输出: &quot;3033459&quot;</code></pre></blockquote><h3 id="解答-38"><a href="#解答-38" class="headerlink" title="解答"></a>解答</h3><p>设数组 nums 中任意两数字的字符串为 x 和 y ，则规定 <strong>排序判断规则</strong> 为：</p><ul><li>若拼接字符串 xy &gt; yx ，则 x 应在 y 右边 ；</li><li>反之，若 xy &lt; yx，则 x 应在 y 左边 ；</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> String <span class="token function">minNumber</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> nums<span class="token punctuation">)</span> <span class="token punctuation">{</span>      String<span class="token punctuation">[</span><span class="token punctuation">]</span> strs <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">String</span><span class="token punctuation">[</span>nums<span class="token punctuation">.</span>length<span class="token punctuation">]</span><span class="token punctuation">;</span>      <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> nums<span class="token punctuation">.</span>length<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span><span class="token punctuation">{</span>        strs<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> String<span class="token punctuation">.</span><span class="token function">valueOf</span><span class="token punctuation">(</span>nums<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token punctuation">}</span>      Arrays<span class="token punctuation">.</span><span class="token function">sort</span><span class="token punctuation">(</span>strs<span class="token punctuation">,</span> <span class="token punctuation">(</span>x<span class="token punctuation">,</span> y<span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">(</span>x <span class="token operator">+</span> y<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">compareTo</span><span class="token punctuation">(</span>y <span class="token operator">+</span> x<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      StringBuilder res <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">StringBuilder</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token keyword">for</span><span class="token punctuation">(</span>String s <span class="token operator">:</span> strs<span class="token punctuation">)</span><span class="token punctuation">{</span>        res<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span>s<span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token punctuation">}</span>      <span class="token keyword">return</span> res<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="复杂度-28"><a href="#复杂度-28" class="headerlink" title="复杂度"></a>复杂度</h3><ul><li><strong>时间复杂度 O(Nlog⁡N) ：</strong>N 为最终返回值的字符数量；使用内置函数 Arrays.sort 的平均时间复杂度为 O(Nlog⁡N)，最差为 O(N<sup>2</sup>)。</li><li><strong>空间复杂度 O(N) ：</strong> 字符串列表 strs 占用线性大小的额外空间。</li></ul><h2 id="61-扑克牌中的顺子"><a href="#61-扑克牌中的顺子" class="headerlink" title="61. 扑克牌中的顺子"></a>61. 扑克牌中的顺子</h2><h3 id="题目-39"><a href="#题目-39" class="headerlink" title="题目"></a>题目</h3><blockquote><p>从若干副扑克牌中随机抽 5 张牌，判断是不是一个顺子，即这5张牌是不是连续的。2～10为数字本身，A为1，J为11，Q为12，K为13，而大、小王为 0 ，可以看成任意数字。A 不能视为 14。</p><p>示例 1:</p><pre><code>输入: [1,2,3,4,5]输出: True</code></pre><p>示例 2:</p><pre><code>输入: [0,0,1,2,5]输出: True</code></pre></blockquote><h3 id="解答-39"><a href="#解答-39" class="headerlink" title="解答"></a>解答</h3><p><img src="https://img.jwt1399.top/img/202208271513742.png"></p><ul><li>遍历五张牌，遇到大小王（即 0 ）直接跳过。</li><li>判别重复： 利用 Set 实现遍历判重， Set 的查找方法的时间复杂度为 O(1) ；</li><li>获取最大 &#x2F; 最小的牌： 借助辅助变量 max 和 min ，遍历统计即可。</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">boolean</span> <span class="token function">isStraight</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> nums<span class="token punctuation">)</span> <span class="token punctuation">{</span>      Set<span class="token operator">&lt;</span>Integer<span class="token operator">></span> set <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">HashSet</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token keyword">int</span> max <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">,</span> min <span class="token operator">=</span> <span class="token number">14</span><span class="token punctuation">;</span>      <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> num <span class="token operator">:</span> nums<span class="token punctuation">)</span><span class="token punctuation">{</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>num <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">continue</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 跳过大小王</span>        max <span class="token operator">=</span> Math<span class="token punctuation">.</span><span class="token function">max</span><span class="token punctuation">(</span>max<span class="token punctuation">,</span>num<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 最大牌</span>        min <span class="token operator">=</span> Math<span class="token punctuation">.</span><span class="token function">min</span><span class="token punctuation">(</span>min<span class="token punctuation">,</span>num<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 最小牌</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>set<span class="token punctuation">.</span><span class="token function">contains</span><span class="token punctuation">(</span>num<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 若有重复，提前返回 false</span>        set<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>num<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 添加此牌至 Set</span>      <span class="token punctuation">}</span>      <span class="token keyword">return</span> max <span class="token operator">-</span> min <span class="token operator">&lt;</span> <span class="token number">5</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 最大牌 - 最小牌 &lt; 5 则可构成顺子</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="复杂度-29"><a href="#复杂度-29" class="headerlink" title="复杂度"></a>复杂度</h3><ul><li><strong>时间复杂度 O(N)=O(5)=O(1)：</strong> 其中 N 为 nums 长度，本题中 N≡5 ；遍历数组使用 O(N) 时间。</li><li><strong>空间复杂度 O(N)=O(5)=O(1) ：</strong> 用于判重的辅助 Set 使用 O(N) 额外空间。</li></ul><h1 id="第-17-天-排序（中等）"><a href="#第-17-天-排序（中等）" class="headerlink" title="第 17 天 排序（中等）"></a>第 17 天 排序（中等）</h1><h2 id="40-最小的k个数"><a href="#40-最小的k个数" class="headerlink" title="40. 最小的k个数"></a>40. 最小的k个数</h2><h3 id="题目-40"><a href="#题目-40" class="headerlink" title="题目"></a>题目</h3><blockquote><p>输入整数数组 arr ，找出其中最小的 k 个数。例如，输入4、5、1、6、2、7、3、8这8个数字，则最小的4个数字是1、2、3、4。</p><p>示例 1：</p><pre><code>输入：arr = [3,2,1], k = 2输出：[1,2] 或者 [2,1]</code></pre><p>示例 2：</p><pre><code>输入：arr = [0,1,2,1], k = 1输出：[0]</code></pre></blockquote><h3 id="解答-40"><a href="#解答-40" class="headerlink" title="解答"></a>解答</h3><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token function">getLeastNumbers</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> arr<span class="token punctuation">,</span> <span class="token keyword">int</span> k<span class="token punctuation">)</span> <span class="token punctuation">{</span>      <span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> res <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">int</span><span class="token punctuation">[</span>k<span class="token punctuation">]</span><span class="token punctuation">;</span>      Arrays<span class="token punctuation">.</span><span class="token function">sort</span><span class="token punctuation">(</span>arr<span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> k<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span><span class="token punctuation">{</span>        res<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> arr<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">;</span>      <span class="token punctuation">}</span>      <span class="token keyword">return</span> res<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token function">getLeastNumbers</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> arr<span class="token punctuation">,</span> <span class="token keyword">int</span> k<span class="token punctuation">)</span> <span class="token punctuation">{</span>        Arrays<span class="token punctuation">.</span><span class="token function">sort</span><span class="token punctuation">(</span>arr<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> Arrays<span class="token punctuation">.</span><span class="token function">copyOf</span><span class="token punctuation">(</span>arr<span class="token punctuation">,</span> k<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="复杂度-30"><a href="#复杂度-30" class="headerlink" title="复杂度"></a>复杂度</h3><ul><li><strong>时间复杂度 O(Nlog⁡N) ：</strong>N 为数组长度；使用内置函数 Arrays.sort 的平均时间复杂度为 O(Nlog⁡N)，最差为 O(N<sup>2</sup>)。</li><li><strong>空间复杂度 O(N) ：</strong> 数组占用线性大小的额外空间。</li></ul><h2 id="41-数据流中的中位数"><a href="#41-数据流中的中位数" class="headerlink" title="41. 数据流中的中位数"></a>41. 数据流中的中位数</h2><h3 id="题目-41"><a href="#题目-41" class="headerlink" title="题目"></a>题目</h3><blockquote><p>如何得到一个数据流中的中位数？如果从数据流中读出奇数个数值，那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值，那么中位数就是所有数值排序之后中间两个数的平均值。</p><p>例如，</p><p>[2,3,4] 的中位数是 3</p><p>[2,3] 的中位数是 (2 + 3) &#x2F; 2 &#x3D; 2.5</p><p>设计一个支持以下两种操作的数据结构：</p><ul><li>void addNum(int num) - 从数据流中添加一个整数到数据结构中。</li><li>double findMedian() - 返回目前所有元素的中位数。</li></ul><p>示例 1：</p><pre><code>输入：[&quot;MedianFinder&quot;,&quot;addNum&quot;,&quot;addNum&quot;,&quot;findMedian&quot;,&quot;addNum&quot;,&quot;findMedian&quot;][[],[1],[2],[],[3],[]]输出：[null,null,null,1.50000,null,2.00000]</code></pre><p>示例 2：</p><pre><code>输入：[&quot;MedianFinder&quot;,&quot;addNum&quot;,&quot;findMedian&quot;,&quot;addNum&quot;,&quot;findMedian&quot;][[],[2],[],[3],[]]输出：[null,null,2.00000,null,2.50000]</code></pre></blockquote><h3 id="解答-41"><a href="#解答-41" class="headerlink" title="解答"></a>解答</h3><p><img src="https://img.jwt1399.top/img/202208281635614.png"></p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">MedianFinder</span> <span class="token punctuation">{</span>    Queue<span class="token operator">&lt;</span>Integer<span class="token operator">></span> A<span class="token punctuation">,</span> B<span class="token punctuation">;</span>    <span class="token keyword">public</span> <span class="token function">MedianFinder</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        A <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">PriorityQueue</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 小顶堆，保存较大的一半</span>        B <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">PriorityQueue</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">(</span>x<span class="token punctuation">,</span> y<span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">(</span>y <span class="token operator">-</span> x<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 大顶堆，保存较小的一半</span>    <span class="token punctuation">}</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">addNum</span><span class="token punctuation">(</span><span class="token keyword">int</span> num<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>A<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">!=</span> B<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            A<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>num<span class="token punctuation">)</span><span class="token punctuation">;</span>            B<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>A<span class="token punctuation">.</span><span class="token function">poll</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>            B<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>num<span class="token punctuation">)</span><span class="token punctuation">;</span>            A<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>B<span class="token punctuation">.</span><span class="token function">poll</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span>    <span class="token keyword">public</span> <span class="token keyword">double</span> <span class="token function">findMedian</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">return</span> A<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">!=</span> B<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">?</span> A<span class="token punctuation">.</span><span class="token function">peek</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">:</span> <span class="token punctuation">(</span>A<span class="token punctuation">.</span><span class="token function">peek</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> B<span class="token punctuation">.</span><span class="token function">peek</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">/</span> <span class="token number">2.0</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="复杂度-31"><a href="#复杂度-31" class="headerlink" title="复杂度"></a>复杂度</h3><ul><li><strong>时间复杂度：</strong><ul><li><strong>查找中位数 O(1) ：</strong> 获取堆顶元素使用 O(1)时间；</li><li><strong>添加数字 O(log⁡N)：</strong> 堆的插入和弹出操作使用 O(log⁡N) 时间。</li></ul></li><li><strong>空间复杂度 O(N) ：</strong> 其中 N 为数据流中的元素数量，小顶堆 A 和大顶堆 B 最多同时保存 N 个元素。</li></ul><h1 id="第-18-天-搜索与回溯算法（中等）"><a href="#第-18-天-搜索与回溯算法（中等）" class="headerlink" title="第 18 天 搜索与回溯算法（中等）"></a>第 18 天 搜索与回溯算法（中等）</h1><h2 id="55-I-二叉树的深度"><a href="#55-I-二叉树的深度" class="headerlink" title="55 - I. 二叉树的深度"></a>55 - I. 二叉树的深度</h2><h3 id="题目-42"><a href="#题目-42" class="headerlink" title="题目"></a>题目</h3><blockquote><p>输入一棵二叉树的根节点，求该树的深度。从根节点到叶节点依次经过的节点（含根、叶节点）形成树的一条路径，最长路径的长度为树的深度。</p><p>例如：</p><p>给定二叉树 [3,9,20,null,null,15,7]，</p><pre><code>   3  / \ 9  20   /  \  15   7</code></pre><p>返回它的最大深度 3 。</p></blockquote><h3 id="解答-42"><a href="#解答-42" class="headerlink" title="解答"></a>解答</h3><p><img src="https://img.jwt1399.top/img/202208291551122.png"></p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">maxDepth</span><span class="token punctuation">(</span>TreeNode root<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>root <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> Math<span class="token punctuation">.</span><span class="token function">max</span><span class="token punctuation">(</span><span class="token function">maxDepth</span><span class="token punctuation">(</span>root<span class="token punctuation">.</span>left<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">maxDepth</span><span class="token punctuation">(</span>root<span class="token punctuation">.</span>right<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="复杂度-32"><a href="#复杂度-32" class="headerlink" title="复杂度"></a>复杂度</h3><ul><li><strong>时间复杂度 O(N) ：</strong> N 为树的节点数量，计算树的深度需要遍历所有节点。</li><li><strong>空间复杂度 O(N)：</strong> 最差情况下（当树退化为链表时），递归深度可达到 N 。</li></ul><h2 id="55-II-平衡二叉树"><a href="#55-II-平衡二叉树" class="headerlink" title="55 - II. 平衡二叉树"></a>55 - II. 平衡二叉树</h2><h3 id="题目-43"><a href="#题目-43" class="headerlink" title="题目"></a>题目</h3><blockquote><p>输入一棵二叉树的根节点，判断该树是不是平衡二叉树。如果某二叉树中任意节点的左右子树的深度相差不超过1，那么它就是一棵平衡二叉树。</p><p>示例 1:</p><p>给定二叉树 [3,9,20,null,null,15,7]</p><pre><code>  3 / \9  20  /  \ 15   7</code></pre><p>返回 true 。</p><p>示例 2:</p><p>给定二叉树 [1,2,2,3,3,null,null,4,4]</p><pre><code>     1    / \   2   2  / \ 3   3/ \</code></pre><p>   4   4<br>返回 false 。</p></blockquote><h3 id="解答-43"><a href="#解答-43" class="headerlink" title="解答"></a>解答</h3><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">boolean</span> <span class="token function">isBalanced</span><span class="token punctuation">(</span>TreeNode root<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>root <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> Math<span class="token punctuation">.</span><span class="token function">abs</span><span class="token punctuation">(</span><span class="token function">depth</span><span class="token punctuation">(</span>root<span class="token punctuation">.</span>left<span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token function">depth</span><span class="token punctuation">(</span>root<span class="token punctuation">.</span>right<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">&lt;=</span> <span class="token number">1</span> <span class="token operator">&amp;&amp;</span> <span class="token function">isBalanced</span><span class="token punctuation">(</span>root<span class="token punctuation">.</span>left<span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> <span class="token function">isBalanced</span><span class="token punctuation">(</span>root<span class="token punctuation">.</span>right<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">private</span> <span class="token keyword">int</span> <span class="token function">depth</span><span class="token punctuation">(</span>TreeNode root<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>root <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> Math<span class="token punctuation">.</span><span class="token function">max</span><span class="token punctuation">(</span><span class="token function">depth</span><span class="token punctuation">(</span>root<span class="token punctuation">.</span>left<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">depth</span><span class="token punctuation">(</span>root<span class="token punctuation">.</span>right<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="复杂度-33"><a href="#复杂度-33" class="headerlink" title="复杂度"></a>复杂度</h3><ul><li><p><strong>时间复杂度 O(NlogN)：</strong>最差情况下（为 “满二叉树” 时），满二叉树高度的复杂度 O(logN)，各层执行 <code>depth(root)</code> 的时间复杂度为 O(N)，总体时间复杂度 &#x3D;&#x3D; 每层执行复杂度 × 层数复杂度 &#x3D; O(N×logN) 。</p></li><li><p><strong>空间复杂度 O(N)：</strong> 最差情况下（树退化为链表时），系统递归需要使用 O(N) 的栈空间。</p></li></ul><h1 id="第-19-天-搜索与回溯算法（中等）"><a href="#第-19-天-搜索与回溯算法（中等）" class="headerlink" title="第 19 天 搜索与回溯算法（中等）"></a>第 19 天 搜索与回溯算法（中等）</h1><h2 id="64-求1-2-…-n"><a href="#64-求1-2-…-n" class="headerlink" title="64. 求1+2+…+n"></a>64. 求1+2+…+n</h2><h3 id="题目-44"><a href="#题目-44" class="headerlink" title="题目"></a>题目</h3><blockquote><p>求 1+2+…+n ，要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句（A?B:C）。</p><p>示例 1：</p><pre><code>输入: n = 3输出: 6</code></pre><p>示例 2：</p><pre><code>输入: n = 9输出: 45</code></pre></blockquote><h3 id="解答-44"><a href="#解答-44" class="headerlink" title="解答"></a>解答</h3><p>1+2+…+(n−1)+n 的计算方法主要有三种：平均计算、迭代、递归。</p><ul><li>平均计算：(1 + n) * n &#x2F; 2；此计算必须使用 <strong>乘除法</strong> ，因此本方法不可取，直接排除。</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">sumNums</span><span class="token punctuation">(</span><span class="token keyword">int</span> n<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token number">1</span> <span class="token operator">+</span> n<span class="token punctuation">)</span> <span class="token operator">*</span> n <span class="token operator">/</span> <span class="token number">2</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><ul><li>迭代：循环必须使用 while 或 for，因此本方法不可取，直接排除。</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">sumNums</span><span class="token punctuation">(</span><span class="token keyword">int</span> n<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">int</span> res <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>    <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> i <span class="token operator">&lt;=</span> n<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span>        res <span class="token operator">+=</span> i<span class="token punctuation">;</span>    <span class="token keyword">return</span> res<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>递归：终止条件需要使用 if ，因此本方法不可取。</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">sumNums</span><span class="token punctuation">(</span><span class="token keyword">int</span> n<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">if</span><span class="token punctuation">(</span>n <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span>    n <span class="token operator">+=</span> <span class="token function">sumNums</span><span class="token punctuation">(</span>n <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> n<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>正确解答：使用逻辑运算符的短路效应来终止递归</li></ul><p>常见的逻辑运算符有三种，即 “与 &amp;&amp; ”，“或 || ”，“非 ! ” ；而其有重要的短路效应</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">if</span><span class="token punctuation">(</span>A <span class="token operator">&amp;&amp;</span> B<span class="token punctuation">)</span>  <span class="token comment" spellcheck="true">// 若 A 为 false ，则 B 的判断不会执行（即短路），直接判定 A &amp;&amp; B 为 false</span><span class="token keyword">if</span><span class="token punctuation">(</span>A <span class="token operator">||</span> B<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// 若 A 为 true ，则 B 的判断不会执行（即短路），直接判定 A || B 为 true</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>本题需要实现 “当 n=1 时终止递归” 的需求，可通过短路效应实现。</p><pre class="line-numbers language-java"><code class="language-java">n <span class="token operator">></span> <span class="token number">0</span> <span class="token operator">&amp;&amp;</span> n <span class="token operator">+=</span> <span class="token function">sumNums</span><span class="token punctuation">(</span>n <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// 当 n = 0 时 n > 0 不成立 ，此时 “短路” ，终止后续递归</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">sumNums</span><span class="token punctuation">(</span><span class="token keyword">int</span> n<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">boolean</span> flag <span class="token operator">=</span> n <span class="token operator">></span> <span class="token number">0</span> <span class="token operator">&amp;&amp;</span> <span class="token punctuation">(</span>n <span class="token operator">+=</span> <span class="token function">sumNums</span><span class="token punctuation">(</span>n <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> n<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="复杂度-34"><a href="#复杂度-34" class="headerlink" title="复杂度"></a>复杂度</h3><ul><li><strong>时间复杂度 O(n) ：</strong> 计算 n+(n−1)+…+2+1 需要开启 n 个递归函数。</li><li><strong>空间复杂度 O(n) ：</strong> 递归深度达到 n ，系统使用 O(n) 大小的额外空间。</li></ul><h2 id="68-I-二叉搜索树的最近公共祖先"><a href="#68-I-二叉搜索树的最近公共祖先" class="headerlink" title="68 - I. 二叉搜索树的最近公共祖先"></a>68 - I. 二叉搜索树的最近公共祖先</h2><h3 id="题目-45"><a href="#题目-45" class="headerlink" title="题目"></a>题目</h3><blockquote><p>给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。</p><p>百度百科中最近公共祖先的定义为：“对于有根树 T 的两个结点 p、q，最近公共祖先表示为一个结点 x，满足 x 是 p、q 的祖先且 x 的深度尽可能大（一个节点也可以是它自己的祖先）。”</p><p>例如，给定如下二叉搜索树:  root &#x3D; [6,2,8,0,4,7,9,null,null,3,5]</p><p><img src="https://img.jwt1399.top/img/202208301248898.png"></p><p>示例 1:</p><pre><code>输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8输出: 6 解释: 节点 2 和节点 8 的最近公共祖先是 6。</code></pre><p>示例 2:</p><pre><code>输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4输出: 2解释: 节点 2 和节点 4 的最近公共祖先是 2, 因为根据定义最近公共祖先节点可以为节点本身。</code></pre></blockquote><h3 id="解答-45"><a href="#解答-45" class="headerlink" title="解答"></a>解答</h3><h4 id="方法1：迭代"><a href="#方法1：迭代" class="headerlink" title="方法1：迭代"></a>方法1：迭代</h4><p><strong>最近公共祖先的定义：</strong> 设节点 root 为节点 p,q 的某公共祖先，若其左子节点 root.left 和右子节点 root.right 都不是 p,q 的公共祖先，则称 root 是 “最近的公共祖先” 。</p><ul><li><strong>循环搜索：</strong> 当节点 root 为空时跳出；<ol><li>当 p,q 都在 root 的 <strong>右子树</strong> 中，则遍历至 root.right ；</li><li>否则，当 p,q 都在 root 的 <strong>左子树</strong> 中，则遍历至 root.left ；</li><li>否则，说明找到了 <strong>最近公共祖先</strong> ，跳出。</li></ol></li><li><strong>返回值：</strong> 最近公共祖先 root 。</li></ul><p>本题给定了两个重要条件：① 树为 <strong>二叉搜索树</strong> ，② 树的所有节点的值都是 <strong>唯一</strong> 的。根据以上条件，可方便地判断 p,q 与 root 的子树关系，即：</p><ul><li>若 root.val&lt;p.val ，则 p 在 root <strong>右子树</strong> 中；</li><li>若 root.val&gt;p.val ，则 p 在 root <strong>左子树</strong> 中；</li><li>若 root.val=p.val ，则 p 和 root 指向 <strong>同一节点</strong></li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> TreeNode <span class="token function">lowestCommonAncestor</span><span class="token punctuation">(</span>TreeNode root<span class="token punctuation">,</span> TreeNode p<span class="token punctuation">,</span> TreeNode q<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">while</span><span class="token punctuation">(</span>root <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token keyword">if</span><span class="token punctuation">(</span>root<span class="token punctuation">.</span>val <span class="token operator">&lt;</span> p<span class="token punctuation">.</span>val <span class="token operator">&amp;&amp;</span> root<span class="token punctuation">.</span>val <span class="token operator">&lt;</span> q<span class="token punctuation">.</span>val<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// p,q 都在 root 的右子树中</span>                root <span class="token operator">=</span> root<span class="token punctuation">.</span>right<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 遍历至右子节点</span>            <span class="token keyword">else</span> <span class="token keyword">if</span><span class="token punctuation">(</span>root<span class="token punctuation">.</span>val <span class="token operator">></span> p<span class="token punctuation">.</span>val <span class="token operator">&amp;&amp;</span> root<span class="token punctuation">.</span>val <span class="token operator">></span> q<span class="token punctuation">.</span>val<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// p,q 都在 root 的左子树中</span>                root <span class="token operator">=</span> root<span class="token punctuation">.</span>left<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 遍历至左子节点</span>            <span class="token keyword">else</span> <span class="token keyword">break</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> root<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="方法2：递归-1"><a href="#方法2：递归-1" class="headerlink" title="方法2：递归"></a>方法2：递归</h4><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> TreeNode <span class="token function">lowestCommonAncestor</span><span class="token punctuation">(</span>TreeNode root<span class="token punctuation">,</span> TreeNode p<span class="token punctuation">,</span> TreeNode q<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>root<span class="token punctuation">.</span>val <span class="token operator">&lt;</span> p<span class="token punctuation">.</span>val <span class="token operator">&amp;&amp;</span> root<span class="token punctuation">.</span>val <span class="token operator">&lt;</span> q<span class="token punctuation">.</span>val<span class="token punctuation">)</span>            <span class="token keyword">return</span> <span class="token function">lowestCommonAncestor</span><span class="token punctuation">(</span>root<span class="token punctuation">.</span>right<span class="token punctuation">,</span> p<span class="token punctuation">,</span> q<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>root<span class="token punctuation">.</span>val <span class="token operator">></span> p<span class="token punctuation">.</span>val <span class="token operator">&amp;&amp;</span> root<span class="token punctuation">.</span>val <span class="token operator">></span> q<span class="token punctuation">.</span>val<span class="token punctuation">)</span>            <span class="token keyword">return</span> <span class="token function">lowestCommonAncestor</span><span class="token punctuation">(</span>root<span class="token punctuation">.</span>left<span class="token punctuation">,</span> p<span class="token punctuation">,</span> q<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> root<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="复杂度-35"><a href="#复杂度-35" class="headerlink" title="复杂度"></a>复杂度</h3><h4 id="方法1-6"><a href="#方法1-6" class="headerlink" title="方法1"></a>方法1</h4><ul><li><strong>时间复杂度 O(N)：</strong> 其中 N 为二叉树节点数；每循环一轮排除一层，二叉搜索树的层数最小为 log⁡N （满二叉树），最大为 N （退化为链表）。</li><li><strong>空间复杂度 O(1)：</strong> 使用常数大小的额外空间。</li></ul><h4 id="方法2-6"><a href="#方法2-6" class="headerlink" title="方法2"></a>方法2</h4><ul><li><strong>时间复杂度 O(N)：</strong> 其中 N 为二叉树节点数；每循环一轮排除一层，二叉搜索树的层数最小为 log⁡N （满二叉树），最大为 N （退化为链表）。</li><li><strong>空间复杂度 O(N)：</strong> 最差情况下，即树退化为链表时，递归深度达到树的层数 N 。</li></ul><h2 id="68-II-二叉树的最近公共祖先"><a href="#68-II-二叉树的最近公共祖先" class="headerlink" title="68 - II. 二叉树的最近公共祖先"></a>68 - II. 二叉树的最近公共祖先</h2><h3 id="题目-46"><a href="#题目-46" class="headerlink" title="题目"></a>题目</h3><blockquote><p>给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。</p><p>百度百科中最近公共祖先的定义为：“对于有根树 T 的两个结点 p、q，最近公共祖先表示为一个结点 x，满足 x 是 p、q 的祖先且 x 的深度尽可能大（一个节点也可以是它自己的祖先）。”</p><p>例如，给定如下二叉树:  root &#x3D; [3,5,1,6,2,0,8,null,null,7,4]</p><p><img src="https://img.jwt1399.top/img/202208301726689.png"></p><p>示例 1:</p><pre><code>输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1输出: 3解释: 节点 5 和节点 1 的最近公共祖先是节点 3。</code></pre><p>示例 2:</p><pre><code>输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4输出: 5解释: 节点 5 和节点 4 的最近公共祖先是节点 5。因为根据定义最近公共祖先节点可以为节点本身。</code></pre></blockquote><h3 id="解答-46"><a href="#解答-46" class="headerlink" title="解答"></a>解答</h3><pre class="line-numbers language-java"><code class="language-java"><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><h3 id="复杂度-36"><a href="#复杂度-36" class="headerlink" title="复杂度"></a>复杂度</h3><h1 id="第-20-天-分治算法（中等）"><a href="#第-20-天-分治算法（中等）" class="headerlink" title="第 20 天 分治算法（中等）"></a>第 20 天 分治算法（中等）</h1><h2 id="07-重建二叉树"><a href="#07-重建二叉树" class="headerlink" title="07. 重建二叉树"></a>07. 重建二叉树</h2><h3 id="题目-47"><a href="#题目-47" class="headerlink" title="题目"></a>题目</h3><blockquote><p>输入某二叉树的前序遍历和中序遍历的结果，请构建该二叉树并返回其根节点。</p><p>假设输入的前序遍历和中序遍历的结果中都不含重复的数字。</p><p>示例 1:</p><p><img src="https://img.jwt1399.top/img/202208311403683.jpg"></p><pre><code>Input: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]Output: [3,9,20,null,null,15,7]</code></pre><p>示例 2:</p><pre><code>Input: preorder = [-1], inorder = [-1]Output: [-1]</code></pre></blockquote><h3 id="解答-47"><a href="#解答-47" class="headerlink" title="解答"></a>解答</h3><p>前序遍历性质： 节点按照 <code>[ 根节点 | 左子树 | 右子树 ]</code> 排序。<br>中序遍历性质： 节点按照 <code>[ 左子树 | 根节点 | 右子树 ]</code> 排序。</p><p><img src="https://img.jwt1399.top/img/202208311418630.png"></p><p><strong>递推参数：</strong> 根节点在前序遍历的索引 <code>pre_root</code> 、子树在中序遍历的左边界 <code>in_left</code> 、子树在中序遍历的右边界 <code>in_right</code> ；根节点在中序遍历 <code>inorder</code> 中的索引 <code>i</code> </p><table><thead><tr><th></th><th>根节点索引</th><th>中序遍历左边界</th><th>中序遍历右边界</th></tr></thead><tbody><tr><td><strong>左子树</strong></td><td><code>pre_root + 1</code></td><td><code>in_left</code></td><td><code>i - 1</code></td></tr><tr><td><strong>右子树</strong></td><td><code>pre_root + i - in_left + 1</code></td><td><code>i + 1</code></td><td><code>in_right</code></td></tr></tbody></table><blockquote><p><strong>TIPS：</strong> <code>pre_root + i - in_left + 1</code>含义为 <code>根节点索引 + 左子树长度 + 1</code></p></blockquote><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> preorder<span class="token punctuation">;</span><span class="token comment" spellcheck="true">//保留的先序遍历，方便递归时依据索引查看先序遍历的值</span>    HashMap<span class="token operator">&lt;</span>Integer<span class="token punctuation">,</span> Integer<span class="token operator">></span> dic <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">HashMap</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//存储中序遍历的值与索引</span>    <span class="token keyword">public</span> TreeNode <span class="token function">buildTree</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> preorder<span class="token punctuation">,</span> <span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> inorder<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">this</span><span class="token punctuation">.</span>preorder <span class="token operator">=</span> preorder<span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">//将中序遍历的值及索引放在map中，方便递归时获取左子树与右子树的数量及其根的索引</span>        <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> inorder<span class="token punctuation">.</span>length<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span>            dic<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span>inorder<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">,</span> i<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> <span class="token function">recur</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> inorder<span class="token punctuation">.</span>length <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//recur(当前根的的索引,子树左边界,子树右边界)</span>    <span class="token punctuation">}</span>    TreeNode <span class="token function">recur</span><span class="token punctuation">(</span><span class="token keyword">int</span> pre_root<span class="token punctuation">,</span> <span class="token keyword">int</span> in_left<span class="token punctuation">,</span> <span class="token keyword">int</span> in_right<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>in_left <span class="token operator">></span> in_right<span class="token punctuation">)</span> <span class="token keyword">return</span> null<span class="token punctuation">;</span>                        <span class="token comment" spellcheck="true">// 递归终止</span>        TreeNode root <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">TreeNode</span><span class="token punctuation">(</span>preorder<span class="token punctuation">[</span>pre_root<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>          <span class="token comment" spellcheck="true">// 建立根节点</span>        <span class="token keyword">int</span> i <span class="token operator">=</span> dic<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>preorder<span class="token punctuation">[</span>pre_root<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                       <span class="token comment" spellcheck="true">// 划分根节点、左子树、右子树</span>        <span class="token comment" spellcheck="true">//左子树的根的索引为先序中的根节点+1 </span>        <span class="token comment" spellcheck="true">//左子树的左边界为原来的中序in_left</span>        <span class="token comment" spellcheck="true">//左子树的右边界为中序中的根节点索引-1</span>        root<span class="token punctuation">.</span>left <span class="token operator">=</span> <span class="token function">recur</span><span class="token punctuation">(</span>pre_root <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">,</span> in_left<span class="token punctuation">,</span> i <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>           <span class="token comment" spellcheck="true">// 开启左子树递归</span>        <span class="token comment" spellcheck="true">//右子树的根的索引为先序中的当前根位置 + 左子树的长度 + 1</span>        <span class="token comment" spellcheck="true">//右子树的左边界为中序中当前根节点+1</span>        <span class="token comment" spellcheck="true">//右子树的右边界为中序中原来右子树的边界</span>        root<span class="token punctuation">.</span>right <span class="token operator">=</span> <span class="token function">recur</span><span class="token punctuation">(</span>pre_root <span class="token operator">+</span> i <span class="token operator">-</span> in_left <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">,</span> i <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">,</span> in_right<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 开启右子树递归</span>        <span class="token keyword">return</span> root<span class="token punctuation">;</span>                                           <span class="token comment" spellcheck="true">// 回溯返回根节点</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="复杂度-37"><a href="#复杂度-37" class="headerlink" title="复杂度"></a>复杂度</h3><ul><li><strong>时间复杂度 O(N) ：</strong> 其中 N 为树的节点数量。初始化 HashMap 需遍历 <code>inorder</code> ，占用 O(N)。递归共建立 N 个节点，每层递归中的节点建立、搜索操作占用 O(1)，因此使用 O(N)时间。</li><li><strong>空间复杂度 O(N) ：</strong> HashMap 使用 O(N) 额外空间；最差情况下（输入二叉树为链表时），递归深度达到 N ，占用 O(N) 的栈帧空间；因此总共使用 O(N) 空间。</li></ul><h2 id="16-数值的整数次方"><a href="#16-数值的整数次方" class="headerlink" title="16. 数值的整数次方"></a>16. 数值的整数次方</h2><h3 id="题目-48"><a href="#题目-48" class="headerlink" title="题目"></a>题目</h3><blockquote><p>实现 pow(x, n) ，即计算 x 的 n 次幂函数（即，x<sup>n</sup>）。不得使用库函数，同时不需要考虑大数问题。</p><p>示例 1：</p><pre><code>输入：x = 2.00000, n = 10输出：1024.00000</code></pre><p>示例 2：</p><pre><code>输入：x = 2.10000, n = 3输出：9.26100</code></pre><p>示例 3：</p><pre><code>输入：x = 2.00000, n = -2输出：0.25000解释：2-2 = 1/22 = 1/4 = 0.25</code></pre></blockquote><h3 id="解答-48"><a href="#解答-48" class="headerlink" title="解答"></a>解答</h3><ul><li>对于任何十进制正整数 n ，设其二进制为 “b<sub>m</sub>…b<sub>3</sub>b<sub>2</sub>b<sub>1</sub>“</li><li>n &#x3D; 1b<sub>1</sub> + 2b<sub>2</sub> + 4b<sub>3</sub> + … + 2<sup>m−1</sup>b<sub>m</sub></li><li>x<sup>n</sup> &#x3D; x<sup>1b<sub>1</sub> + 2b<sub>2</sub> + 4b<sup>3</sup> + … + 2<sup>m−1</sup>b<sub>m</sub></sup> &#x3D; x<sup>1b<sub>1</sub></sup>x<sup>2b<sub>2</sub></sup>x<sup>4b<sup>3</sup></sup>…x<sup>2<sup>m−1</sup>b<sub>m</sub></sup></li></ul><p>根据以上推导，可把计算 x<sup>n</sup> 转化为解决以下两个问题：</p><ul><li><strong>计算 x<sup>1</sup>,x<sup>2</sup>,x<sup>4</sup>,…,x<sup>2m−1</sup>的值：</strong> 循环赋值操作 x &#x3D; x<sup>2</sup> 即可；</li><li><strong>获取二进制各位 b<sub>1</sub>,b<sub>2</sub>,b<sub>3</sub>,…,b<sub>m</sub> 的值：</strong> 循环执行以下操作即可。<ol><li><strong>n&amp;1 （与操作）：</strong> 判断 n 二进制最右一位是否为 1 ；</li><li><strong>n&gt;&gt;1 （移位操作）：</strong> n 右移一位（可理解为删除最后一位）。</li></ol></li></ul><blockquote><p>Java 代码中 <code>int32</code> 变量 n∈[−2147483648,2147483647]，因此当 n=−2147483648 时执行 n = −n 会因越界而赋值出错。解决方法是先将 n 存入 <code>long</code> 变量 b ，后面用 b 操作即可。</p></blockquote><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">double</span> <span class="token function">myPow</span><span class="token punctuation">(</span><span class="token keyword">double</span> x<span class="token punctuation">,</span> <span class="token keyword">int</span> n<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>x <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span>        <span class="token keyword">long</span> b <span class="token operator">=</span> n<span class="token punctuation">;</span>        <span class="token keyword">double</span> res <span class="token operator">=</span> <span class="token number">1.0</span><span class="token punctuation">;</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>b <span class="token operator">&lt;</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            x <span class="token operator">=</span> <span class="token number">1</span> <span class="token operator">/</span> x<span class="token punctuation">;</span>            b <span class="token operator">=</span> <span class="token operator">-</span>b<span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">while</span><span class="token punctuation">(</span>b <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token keyword">if</span><span class="token punctuation">(</span><span class="token punctuation">(</span>b <span class="token operator">&amp;</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token number">1</span><span class="token punctuation">)</span> res <span class="token operator">*=</span> x<span class="token punctuation">;</span>            x <span class="token operator">*=</span> x<span class="token punctuation">;</span>            b <span class="token operator">>>=</span> <span class="token number">1</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> res<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>如果 n 为偶数： x<sup>n</sup> &#x3D;  x<sup>n&#x2F;2</sup> * x<sup>n&#x2F;2</sup>* x<sup>n%2</sup></li><li>如果 n 为奇数： x<sup>n</sup> &#x3D;  x<sup>n&#x2F;2</sup> * x<sup>n&#x2F;2</sup> * x<sup>n%2</sup></li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">double</span> <span class="token function">myPow</span><span class="token punctuation">(</span><span class="token keyword">double</span> x<span class="token punctuation">,</span> <span class="token keyword">int</span> n<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>n <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token number">1</span><span class="token punctuation">;</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>n <span class="token operator">==</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token keyword">return</span> x<span class="token punctuation">;</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>n <span class="token operator">==</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token number">1</span> <span class="token operator">/</span> x<span class="token punctuation">;</span>        <span class="token keyword">double</span> half <span class="token operator">=</span> <span class="token function">myPow</span><span class="token punctuation">(</span>x<span class="token punctuation">,</span> n <span class="token operator">/</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">double</span> mod <span class="token operator">=</span> <span class="token function">myPow</span><span class="token punctuation">(</span>x<span class="token punctuation">,</span> n <span class="token operator">%</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> half <span class="token operator">*</span> half <span class="token operator">*</span> mod<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="复杂度-38"><a href="#复杂度-38" class="headerlink" title="复杂度"></a>复杂度</h3><ul><li><strong>时间复杂度 O(log<sub>2</sub>n)：</strong> 二分的时间复杂度为对数级别。</li><li><strong>空间复杂度 O(1) ：</strong> res, b 等变量占用常数大小额外空间。</li></ul><h2 id="33-二叉搜索树的后序遍历序列"><a href="#33-二叉搜索树的后序遍历序列" class="headerlink" title="33. 二叉搜索树的后序遍历序列"></a>33. 二叉搜索树的后序遍历序列</h2><h3 id="题目-49"><a href="#题目-49" class="headerlink" title="题目"></a>题目</h3><blockquote><p>输入一个整数数组，判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 true，否则返回 false。假设输入的数组的任意两个数字都互不相同。</p><p>参考以下这颗二叉搜索树：</p><pre><code>    5   / \  2   6 / \1   3</code></pre><p>示例 1：</p><pre><code>输入: [1,6,3,2,5]输出: false</code></pre><p>示例 2：</p><pre><code>输入: [1,3,2,6,5]输出: true</code></pre></blockquote><h3 id="解答-49"><a href="#解答-49" class="headerlink" title="解答"></a>解答</h3><ul><li><strong>后序遍历定义：</strong> <code>[ 左子树 | 右子树 | 根节点 ]</code> ，即遍历顺序为 “左、右、根” 。</li></ul><p><img src="https://img.jwt1399.top/img/202209012207166.png"></p><h5 id="递归解析："><a href="#递归解析：" class="headerlink" title="递归解析："></a>递归解析：</h5><ul><li><p><strong>终止条件：</strong> 当 left ≥ right，说明此子树节点数量 ≤ 1 ，无需判别正确性，因此直接返回 true；</p></li><li><p><strong>递推工作：</strong></p><ol><li><p><strong>划分左右子树：</strong> 遍历后序遍历的 [left,right] 区间元素，寻找 <strong>第一个大于根节点</strong> 的节点，索引记为 m 。</p><ul><li>此时，可划分出左子树区间 [left,m−1]</li></ul></li></ol><ul><li>右子树区间 [m,right−1]</li><li>根节点索引 right</li></ul><ol start="2"><li><p><strong>判断是否为二叉搜索树：</strong></p><ul><li><strong>左子树区间</strong> [left,m−1] 内的所有节点都应 &lt; postorder[right]。</li><li><strong>右子树区间</strong> [m,right−1] 内的所有节点都应 &gt; postorder[right] 。</li></ul></li></ol></li><li><p><strong>返回值：</strong> 所有子树都需正确才可判定正确，因此使用 <strong>与逻辑符</strong> &amp;&amp; 连接。</p><ol><li><strong>p &#x3D; right ：</strong> 判断 <strong>此树</strong> 是否正确。</li><li><strong>recur(left,m−1) ：</strong> 判断 <strong>此树的左子树</strong> 是否正确。</li><li><strong>recur(m,right−1)：</strong> 判断 <strong>此树的右子树</strong> 是否正确。</li></ol></li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">boolean</span> <span class="token function">verifyPostorder</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> postorder<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">return</span> <span class="token function">recur</span><span class="token punctuation">(</span>postorder<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> postorder<span class="token punctuation">.</span>length <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">boolean</span> <span class="token function">recur</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> postorder<span class="token punctuation">,</span> <span class="token keyword">int</span> left<span class="token punctuation">,</span> <span class="token keyword">int</span> right<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>left <span class="token operator">>=</span> right<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>        <span class="token keyword">int</span> rootValue <span class="token operator">=</span> postorder<span class="token punctuation">[</span>right<span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 根节点的值</span>        <span class="token keyword">int</span> p <span class="token operator">=</span> left<span class="token punctuation">;</span>        <span class="token keyword">while</span><span class="token punctuation">(</span>postorder<span class="token punctuation">[</span>p<span class="token punctuation">]</span> <span class="token operator">&lt;</span> rootValue<span class="token punctuation">)</span> p<span class="token operator">++</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//左子树</span>        <span class="token keyword">int</span> m <span class="token operator">=</span> p<span class="token punctuation">;</span>        <span class="token keyword">while</span><span class="token punctuation">(</span>postorder<span class="token punctuation">[</span>p<span class="token punctuation">]</span> <span class="token operator">></span> rootValue<span class="token punctuation">)</span> p<span class="token operator">++</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//右子树</span>        <span class="token keyword">return</span> p <span class="token operator">==</span> right <span class="token operator">&amp;&amp;</span> <span class="token function">recur</span><span class="token punctuation">(</span>postorder<span class="token punctuation">,</span> left<span class="token punctuation">,</span> m <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> <span class="token function">recur</span><span class="token punctuation">(</span>postorder<span class="token punctuation">,</span> m<span class="token punctuation">,</span> right <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="复杂度-39"><a href="#复杂度-39" class="headerlink" title="复杂度"></a>复杂度</h3><ul><li><strong>时间复杂度 O(N<sup>2</sup>) ：</strong> 每次调用 recur(left,right) 减去一个根节点，因此递归占用 O(N) ；最差情况下（即当树退化为链表），每轮递归都需遍历树所有节点，占用 O(N) 。</li><li><strong>空间复杂度 O(N) ：</strong> 最差情况下（即当树退化为链表），递归深度将达到 N 。</li></ul><h1 id="第-21-天-位运算（简单）"><a href="#第-21-天-位运算（简单）" class="headerlink" title="第 21 天 位运算（简单）"></a>第 21 天 位运算（简单）</h1><h2 id="15-二进制中1的个数"><a href="#15-二进制中1的个数" class="headerlink" title="15. 二进制中1的个数"></a>15. 二进制中1的个数</h2><h3 id="题目-50"><a href="#题目-50" class="headerlink" title="题目"></a>题目</h3><blockquote><p>编写一个函数，输入是一个无符号整数（以二进制串的形式），返回其二进制表达式中数字位数为 ‘1’ 的个数（也被称为汉明重量)。</p><p>提示：</p><p>请注意，在某些语言（如 Java）中，没有无符号整数类型。在这种情况下，输入和输出都将被指定为有符号整数类型，并且不应影响您的实现，因为无论整数是有符号的还是无符号的，其内部的二进制表示形式都是相同的。<br>在 Java 中，编译器使用 二进制补码 记法来表示有符号整数。因此，在上面的 示例 3 中，输入表示有符号整数 -3。</p><p>示例 1：</p><pre><code>输入：n = 11 (控制台输入 00000000000000000000000000001011)输出：3解释：输入的二进制串 00000000000000000000000000001011 中，共有三位为 &#39;1&#39;。</code></pre><p>示例 2：</p><pre><code>输入：n = 128 (控制台输入 00000000000000000000000010000000)输出：1解释：输入的二进制串 00000000000000000000000010000000 中，共有一位为 &#39;1&#39;。</code></pre><p>示例 3：</p><pre><code>输入：n = 4294967293 (控制台输入 11111111111111111111111111111101，部分语言中 n = -3）输出：31解释：输入的二进制串 11111111111111111111111111111101 中，共有 31 位为 &#39;1&#39;。</code></pre></blockquote><h3 id="解答-50"><a href="#解答-50" class="headerlink" title="解答"></a>解答</h3><h4 id="方法1：调用API-1"><a href="#方法1：调用API-1" class="headerlink" title="方法1：调用API"></a>方法1：调用API</h4><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// you need to treat n as an unsigned value</span>    <span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">hammingWeight</span><span class="token punctuation">(</span><span class="token keyword">int</span> n<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">return</span> Integer<span class="token punctuation">.</span><span class="token function">bitCount</span><span class="token punctuation">(</span>n<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="方法2：逐位判断"><a href="#方法2：逐位判断" class="headerlink" title="方法2：逐位判断"></a>方法2：<strong>逐位判断</strong></h4><ul><li>根据 <strong>与运算</strong> 定义，设二进制数字 n ，则有：<ul><li>若 n&amp;1=0 ，则 n 二进制 <strong>最右一位</strong> 为 0 ；</li><li>若 n&amp;1=1，则 n 二进制 <strong>最右一位</strong> 为 1 。</li></ul></li><li>根据以上特点，考虑以下 <strong>循环判断</strong> ：<ol><li>判断 n 最右一位是否为 1 ，根据结果计数。</li><li>将 n 右移一位（本题要求把数字 n 看作无符号数，因此使用 <strong>无符号右移</strong>(&gt;&gt;&gt;) 操作）。</li></ol></li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">hammingWeight</span><span class="token punctuation">(</span><span class="token keyword">int</span> n<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">int</span> res <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>        <span class="token keyword">while</span><span class="token punctuation">(</span>n <span class="token operator">!=</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">{</span>            <span class="token keyword">if</span><span class="token punctuation">(</span><span class="token punctuation">(</span>n<span class="token operator">&amp;</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token number">1</span><span class="token punctuation">)</span>                 res<span class="token operator">++</span><span class="token punctuation">;</span>            n <span class="token operator">=</span> n<span class="token operator">>>></span><span class="token number">1</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> res<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">//优化</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">hammingWeight</span><span class="token punctuation">(</span><span class="token keyword">int</span> n<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">int</span> res <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>        <span class="token keyword">while</span><span class="token punctuation">(</span>n <span class="token operator">!=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            res <span class="token operator">+=</span> n <span class="token operator">&amp;</span> <span class="token number">1</span><span class="token punctuation">;</span>            n <span class="token operator">>>>=</span> <span class="token number">1</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> res<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="方法3：巧用-n-amp-n-1"><a href="#方法3：巧用-n-amp-n-1" class="headerlink" title="方法3：巧用 n &amp; (n - 1)"></a>方法3：<strong>巧用</strong> n &amp; (n - 1)</h4><ul><li><strong>(n−1) 解析：</strong> 二进制数字 n 最右边的 1 变成 0 ，此 1 右边的 0 都变成 1 。</li><li><strong>n&amp;(n−1) 解析：</strong> 二进制数字 n 最右边的 1 变成 0 ，其余不变。即消去最右边 1</li></ul><p><img src="https://img.jwt1399.top/img/202209021306142.png"></p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">hammingWeight</span><span class="token punctuation">(</span><span class="token keyword">int</span> n<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">int</span> res <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>        <span class="token keyword">while</span><span class="token punctuation">(</span>n <span class="token operator">!=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            res<span class="token operator">++</span><span class="token punctuation">;</span>            n <span class="token operator">&amp;=</span> n <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> res<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="复杂度-40"><a href="#复杂度-40" class="headerlink" title="复杂度"></a>复杂度</h3><h4 id="方法2-7"><a href="#方法2-7" class="headerlink" title="方法2"></a>方法2</h4><ul><li><strong>时间复杂度 O(log<sub>2</sub>n)：</strong> 此算法循环内部仅有 <strong>移位、与、加</strong> 等基本运算，占用 O(1) ；逐位判断需循环 log<sub>2</sub>n 次，其中 log<sub>2</sub>n 代表数字 n 最高位 1 的所在位数（例如 log<sub>2</sub>4=2, log<sub>2</sub>16&#x3D;4）。</li><li><strong>空间复杂度 O(1) ：</strong> 变量 res 使用常数大小额外空间。</li></ul><h4 id="方法3"><a href="#方法3" class="headerlink" title="方法3"></a>方法3</h4><ul><li><strong>时间复杂度 O(M) ：</strong> 循环内部仅有减法、加、与运算，占用 O(1) ；设 M 为二进制数字 n 中 1 的个数，则需循环 M 次（每轮消去一个 1 ），占用 O(M) 。</li><li><strong>空间复杂度 O(1) ：</strong> 变量 res 使用常数大小额外空间。</li></ul><h2 id="65-不用加减乘除做加法"><a href="#65-不用加减乘除做加法" class="headerlink" title="65. 不用加减乘除做加法"></a>65. 不用加减乘除做加法</h2><h3 id="题目-51"><a href="#题目-51" class="headerlink" title="题目"></a>题目</h3><blockquote><p>写一个函数，求两个整数之和，要求在函数体内不得使用 “+”、“-”、“*”、“&#x2F;” 四则运算符号。</p><p>示例:</p><pre><code>输入: a = 1, b = 1输出: 2</code></pre></blockquote><h3 id="解答-51"><a href="#解答-51" class="headerlink" title="解答"></a>解答</h3><p><img src="https://img.jwt1399.top/img/202209021331863.png"></p><p>因为不能使用“+”，所以不能直接 n + c ，所以循环求 n 和 c，直至进位 c &#x3D; 0；此时 s &#x3D; n ，返回 n 即可。</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">add</span><span class="token punctuation">(</span><span class="token keyword">int</span> a<span class="token punctuation">,</span> <span class="token keyword">int</span> b<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">while</span><span class="token punctuation">(</span>b <span class="token operator">!=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// 当进位为 0 时跳出</span>            <span class="token keyword">int</span> c <span class="token operator">=</span> <span class="token punctuation">(</span>a <span class="token operator">&amp;</span> b<span class="token punctuation">)</span> <span class="token operator">&lt;&lt;</span> <span class="token number">1</span><span class="token punctuation">;</span>  <span class="token comment" spellcheck="true">// c = 进位</span>            <span class="token keyword">int</span> n <span class="token operator">=</span> a <span class="token operator">^</span> b<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// n = 非进位和</span>             a <span class="token operator">=</span> n<span class="token punctuation">;</span>  <span class="token comment" spellcheck="true">// a = 非进位和</span>             b <span class="token operator">=</span> c<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// b = 进位</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> a<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">//优化</span><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">add</span><span class="token punctuation">(</span><span class="token keyword">int</span> a<span class="token punctuation">,</span> <span class="token keyword">int</span> b<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">while</span><span class="token punctuation">(</span>b <span class="token operator">!=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// 当进位为 0 时跳出</span>            <span class="token keyword">int</span> c <span class="token operator">=</span> <span class="token punctuation">(</span>a <span class="token operator">&amp;</span> b<span class="token punctuation">)</span> <span class="token operator">&lt;&lt;</span> <span class="token number">1</span><span class="token punctuation">;</span>  <span class="token comment" spellcheck="true">// c = 进位</span>            a <span class="token operator">^=</span> b<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// a = 非进位和</span>            b <span class="token operator">=</span> c<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// b = 进位</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> a<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="复杂度-41"><a href="#复杂度-41" class="headerlink" title="复杂度"></a>复杂度</h3><ul><li><strong>时间复杂度 O(1) ：</strong> 最差情况下（例如 a= 0x7fffffff , b=1 时），需循环 32 次，使用 O(1) 时间；每轮中的常数次位操作使用 O(1) 时间。</li><li><strong>空间复杂度 O(1) ：</strong> 使用常数大小的额外空间。</li></ul><h1 id="第-22-天-位运算（中等）"><a href="#第-22-天-位运算（中等）" class="headerlink" title="第 22 天 位运算（中等）"></a>第 22 天 位运算（中等）</h1><h2 id="56-I-数组中数字出现的次数"><a href="#56-I-数组中数字出现的次数" class="headerlink" title="56 - I. 数组中数字出现的次数"></a>56 - I. 数组中数字出现的次数</h2><h3 id="题目-52"><a href="#题目-52" class="headerlink" title="题目"></a>题目</h3><blockquote><p>一个整型数组 nums 里除两个数字之外，其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n)，空间复杂度是O(1)。</p><p>示例 1：</p><p>输入：nums &#x3D; [4,1,4,6]<br>输出：[1,6] 或 [6,1]<br>示例 2：</p><p>输入：nums &#x3D; [1,2,10,4,1,4,3,3]<br>输出：[2,10] 或 [10,2]</p></blockquote><h3 id="解答-52"><a href="#解答-52" class="headerlink" title="解答"></a>解答</h3><p>如果问题是：一个整型数组 nums 里<strong>有一个</strong>只出现一次的数字，其他数字都出现了两次。那么可以很容易求出这个数字</p><pre><code>a⊕a⊕b⊕b⊕...⊕x=0⊕0⊕...⊕x=x</code></pre><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token function">singleNumber</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> nums<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">int</span> x <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>    <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> num <span class="token operator">:</span> nums<span class="token punctuation">)</span>  <span class="token comment" spellcheck="true">// 1. 遍历 nums 执行异或运算</span>        x <span class="token operator">^=</span> num<span class="token punctuation">;</span>    <span class="token keyword">return</span> x<span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">// 2. 返回出现一次的数字 x</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>本题难点： 数组 nums <strong>有两个</strong>只出现一次的数字，因此无法通过异或直接得到这两个数字。</p><p>解决思路是将两个数字拆分到两个子数组中，再分别遍历两个子数组执行异或</p><p>设两个只出现一次的数字为 x , y ，由于 x ≠ y，则 x 和 y 二进制至少有一位不同（即分别为 0 和 1 ），根据此位可以将 nums 拆分为分别包含 x 和 y 的两个子数组。</p><pre><code>a⊕a⊕b⊕b⊕...⊕x⊕y  =0⊕0⊕...⊕x⊕y=x⊕y</code></pre><p>根据异或运算定义，若整数 x⊕y 某二进制位为 1 ，则 x 和 y 的此二进制位一定不同。换言之，找到 x⊕y 某位为 1 的二进制位 m，即可将数组 nums 拆分为包含 x 和 y 的两个子数组。</p><ul><li>若 (x⊕y) &amp; 0001 &#x3D; 1 ，则 x⊕y 的右边第一位为 1 ；</li><li>若 (x⊕y) &amp; 0010 &#x3D; 1，则 x⊕y 的右边第二位为 1 ；</li><li>以此类推……</li></ul><p><img src="https://img.jwt1399.top/img/202209031340951.png"></p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token function">singleNumbers</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> nums<span class="token punctuation">)</span> <span class="token punctuation">{</span>      <span class="token keyword">int</span> n <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">,</span> m <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">,</span> x <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">,</span> y <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>      <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> num <span class="token operator">:</span> nums<span class="token punctuation">)</span>        n <span class="token operator">^=</span> num<span class="token punctuation">;</span>      <span class="token keyword">while</span><span class="token punctuation">(</span><span class="token punctuation">(</span>n <span class="token operator">&amp;</span> m<span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span>        m <span class="token operator">&lt;&lt;=</span> <span class="token number">1</span><span class="token punctuation">;</span>      <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> num <span class="token operator">:</span> nums<span class="token punctuation">)</span>        <span class="token keyword">if</span><span class="token punctuation">(</span><span class="token punctuation">(</span>num <span class="token operator">&amp;</span> m<span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span>          x <span class="token operator">^=</span> num<span class="token punctuation">;</span>        <span class="token keyword">else</span> y <span class="token operator">^=</span> num<span class="token punctuation">;</span>      <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">{</span>x<span class="token punctuation">,</span>y<span class="token punctuation">}</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="复杂度-42"><a href="#复杂度-42" class="headerlink" title="复杂度"></a>复杂度</h3><ul><li><strong>时间复杂度 O(N) ：</strong> 线性遍历 nums 使用 O(N) 时间，遍历 x⊕y 二进制位使用 O(32)=O(1) 时间。</li><li><strong>空间复杂度 O(1) ：</strong> 辅助变量 n , m , x , y 使用常数大小额外空间。</li></ul><h2 id="56-II-数组中数字出现的次数-II"><a href="#56-II-数组中数字出现的次数-II" class="headerlink" title="56 - II. 数组中数字出现的次数 II"></a>56 - II. 数组中数字出现的次数 II</h2><h3 id="题目-53"><a href="#题目-53" class="headerlink" title="题目"></a>题目</h3><blockquote><p>在一个数组 nums 中除一个数字只出现一次之外，其他数字都出现了三次。请找出那个只出现一次的数字。</p><p>示例 1：</p><pre><code>输入：nums = [3,4,3,3]输出：4</code></pre><p>示例 2：</p><pre><code>输入：nums = [9,1,7,9,7,9,7]输出：1</code></pre></blockquote><h3 id="解答-53"><a href="#解答-53" class="headerlink" title="解答"></a>解答</h3><h4 id="方法1：哈希表"><a href="#方法1：哈希表" class="headerlink" title="方法1：哈希表"></a>方法1：哈希表</h4><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">singleNumber</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> nums<span class="token punctuation">)</span> <span class="token punctuation">{</span>        Map<span class="token operator">&lt;</span>Integer<span class="token punctuation">,</span> Integer<span class="token operator">></span> map <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">HashMap</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 加入哈希表</span>        <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> nums<span class="token punctuation">.</span>length<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            map<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span>nums<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">,</span> map<span class="token punctuation">.</span><span class="token function">getOrDefault</span><span class="token punctuation">(</span>nums<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 遍历找出第一个次数为1的数</span>        <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> nums<span class="token punctuation">.</span>length<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token keyword">if</span><span class="token punctuation">(</span>map<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>nums<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token keyword">return</span> nums<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="方法2：位运算"><a href="#方法2：位运算" class="headerlink" title="方法2：位运算"></a>方法2：位运算</h4><p><img src="https://img.jwt1399.top/img/202209031429884.png"></p><p>使用 <strong>与运算</strong> ，可获取二进制数字 num 的最右一位 n1 ：</p><p>n1=num&amp;i</p><p>配合 <strong>无符号右移操作</strong> ，可获取 num 所有位的值（即 n1 ~ n32）：</p><p>num=num&gt;&gt;&gt;1</p><p>建立一个长度为 32 的数组 counts ，通过以上方法可记录所有数字的各二进制位的 1 的出现次数。</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> counts <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">int</span><span class="token punctuation">[</span><span class="token number">32</span><span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> nums<span class="token punctuation">.</span>length<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> j <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> j <span class="token operator">&lt;</span> <span class="token number">32</span><span class="token punctuation">;</span> j<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        counts<span class="token punctuation">[</span>j<span class="token punctuation">]</span> <span class="token operator">+=</span> nums<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">&amp;</span> <span class="token number">1</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 更新第 j 位</span>        nums<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">>>>=</span> <span class="token number">1</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 第 j 位 --> 第 j + 1 位</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>将 counts 各元素对 3 求余，则结果为 “只出现一次的数字” 的各二进制位。</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> <span class="token number">32</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    counts<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">%=</span> <span class="token number">3</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 得到 只出现一次的数字 的第 (31 - i) 位 </span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>利用 <strong>左移操作</strong> 和 <strong>或运算</strong> ，可将 counts 数组中各二进位的值恢复到数字 res 上（循环区间是 i∈[0,31]）。</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> counts<span class="token punctuation">.</span>length<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    res <span class="token operator">&lt;&lt;=</span> <span class="token number">1</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 左移 1 位</span>    res <span class="token operator">|=</span> counts<span class="token punctuation">[</span><span class="token number">31</span> <span class="token operator">-</span> i<span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 恢复第 i 位的值到 res</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>最终返回 res 即可。</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">singleNumber</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> nums<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> counts <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">int</span><span class="token punctuation">[</span><span class="token number">32</span><span class="token punctuation">]</span><span class="token punctuation">;</span>        <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> num <span class="token operator">:</span> nums<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> j <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> j <span class="token operator">&lt;</span> <span class="token number">32</span><span class="token punctuation">;</span> j<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                counts<span class="token punctuation">[</span>j<span class="token punctuation">]</span> <span class="token operator">+=</span> num <span class="token operator">&amp;</span> <span class="token number">1</span><span class="token punctuation">;</span>                num <span class="token operator">>>>=</span> <span class="token number">1</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span>        <span class="token keyword">int</span> res <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">,</span> m <span class="token operator">=</span> <span class="token number">3</span><span class="token punctuation">;</span>        <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> <span class="token number">32</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            res <span class="token operator">&lt;&lt;=</span> <span class="token number">1</span><span class="token punctuation">;</span>            res <span class="token operator">|=</span> counts<span class="token punctuation">[</span><span class="token number">31</span> <span class="token operator">-</span> i<span class="token punctuation">]</span> <span class="token operator">%</span> m<span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> res<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="复杂度-43"><a href="#复杂度-43" class="headerlink" title="复杂度"></a>复杂度</h3><h4 id="方法1-7"><a href="#方法1-7" class="headerlink" title="方法1"></a>方法1</h4><ul><li><strong>时间复杂度 O(N) ：</strong> 其中 N 为数组 nums 的长度；遍历数组占用 O(N) </li><li><strong>空间复杂度 O(N) ：</strong>  HashMap 使用 O(N) 额外空间</li></ul><h4 id="方法2-8"><a href="#方法2-8" class="headerlink" title="方法2"></a>方法2</h4><ul><li><strong>时间复杂度 O(N) ：</strong> 其中 NNN 位数组 nums 的长度；遍历数组占用 O(N) ，每轮中的常数个位运算操作占用 O(1) 。</li><li><strong>空间复杂度 O(1) ：</strong> 数组 counts 长度恒为 32 ，占用常数大小的额外空间。</li></ul><h1 id="第-23-天-数学（简单）"><a href="#第-23-天-数学（简单）" class="headerlink" title="第 23 天 数学（简单）"></a>第 23 天 数学（简单）</h1><h2 id="39-数组中出现次数超过一半的"><a href="#39-数组中出现次数超过一半的" class="headerlink" title="39. 数组中出现次数超过一半的"></a>39. 数组中出现次数超过一半的</h2><h3 id="题目-54"><a href="#题目-54" class="headerlink" title="题目"></a>题目</h3><blockquote><p>数组中有一个数字出现的次数超过数组长度的一半，请找出这个数字。</p><p>你可以假设数组是非空的，并且给定的数组总是存在多数元素。</p><p>示例 1:</p><pre><code>输入: [1, 2, 3, 2, 2, 2, 5, 4, 2]输出: 2</code></pre></blockquote><h3 id="解答-54"><a href="#解答-54" class="headerlink" title="解答"></a>解答</h3><h4 id="方法1：哈希表-1"><a href="#方法1：哈希表-1" class="headerlink" title="方法1：哈希表"></a>方法1：哈希表</h4><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">majorityElement</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> nums<span class="token punctuation">)</span> <span class="token punctuation">{</span>        Map<span class="token operator">&lt;</span>Integer<span class="token punctuation">,</span> Integer<span class="token operator">></span> map <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">HashMap</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> nums<span class="token punctuation">.</span>length<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span><span class="token punctuation">{</span>            map<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span>nums<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">,</span> map<span class="token punctuation">.</span><span class="token function">getOrDefault</span><span class="token punctuation">(</span>nums<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> nums<span class="token punctuation">.</span>length<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span><span class="token punctuation">{</span>            <span class="token keyword">if</span><span class="token punctuation">(</span>map<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>nums<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token operator">></span> <span class="token punctuation">(</span>nums<span class="token punctuation">.</span>length<span class="token operator">/</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">{</span>                <span class="token keyword">return</span> nums<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>              <span class="token punctuation">}</span>        <span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">//优化</span><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">majorityElement</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> nums<span class="token punctuation">)</span> <span class="token punctuation">{</span>        Map<span class="token operator">&lt;</span>Integer<span class="token punctuation">,</span> Integer<span class="token operator">></span> map <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">HashMap</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> num <span class="token operator">:</span> nums<span class="token punctuation">)</span><span class="token punctuation">{</span>            map<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span>num<span class="token punctuation">,</span> map<span class="token punctuation">.</span><span class="token function">getOrDefault</span><span class="token punctuation">(</span>num<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">if</span><span class="token punctuation">(</span>map<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>num<span class="token punctuation">)</span> <span class="token operator">></span> nums<span class="token punctuation">.</span>length<span class="token operator">/</span><span class="token number">2</span><span class="token punctuation">)</span>                 <span class="token keyword">return</span> num<span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="方法2：排序"><a href="#方法2：排序" class="headerlink" title="方法2：排序"></a>方法2：排序</h4><p>所求的数字出现次数多于一半，那么排序后必定在中间</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">majorityElement</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> nums<span class="token punctuation">)</span> <span class="token punctuation">{</span>        Arrays<span class="token punctuation">.</span><span class="token function">sort</span><span class="token punctuation">(</span>nums<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> nums<span class="token punctuation">[</span>nums<span class="token punctuation">.</span>length<span class="token operator">/</span><span class="token number">2</span><span class="token punctuation">]</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="方法3：摩尔投票法"><a href="#方法3：摩尔投票法" class="headerlink" title="方法3：摩尔投票法"></a>方法3：摩尔投票法</h4><p>核心理念为 <strong>票数正负抵消</strong>，设输入数组 nums 的众数为 x ，数组长度为 n 。</p><ul><li><p>推论一： 若记 众数 的票数为 +1 ，非众数 的票数为 −1 ，则一定有所有数字的票数和 &gt; 0 </p></li><li><p>推论二： 若数组的前 a 个数字的 票数和 &#x3D;0 ，则 数组剩余 (n-a) 个数字的 票数和一定仍 &gt;0 ，即后 (n-a) 个数字的众数仍为 x</p></li></ul><p><img src="https://img.jwt1399.top/img/202209041206429.png"></p><h5 id="算法流程"><a href="#算法流程" class="headerlink" title="算法流程:"></a>算法流程:</h5><ol><li><strong>初始化：</strong> 票数统计 <code>votes = 0</code> ， 众数 <code>x = 0</code>；</li><li><strong>循环：</strong> 遍历数组 <code>nums</code> 中的每个数字 <code>num</code> ；<ol><li>当 票数 <code>votes</code> 等于 0 ，则假设当前数字 <code>num</code> 是众数；</li><li>当 <code>num = x</code> 时，票数 <code>votes</code> 自增 1 ；当 <code>num != x</code> 时，票数 <code>votes</code> 自减 1 ；</li></ol></li><li><strong>返回值：</strong> 返回 <code>x</code> 即可；</li></ol><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">majorityElement</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> nums<span class="token punctuation">)</span> <span class="token punctuation">{</span>      <span class="token keyword">int</span> votes <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">,</span> x <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>      <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> num <span class="token operator">:</span> nums<span class="token punctuation">)</span><span class="token punctuation">{</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>votes <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> x <span class="token operator">=</span> num<span class="token punctuation">;</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>num <span class="token operator">==</span> x<span class="token punctuation">)</span> votes<span class="token operator">++</span><span class="token punctuation">;</span>        <span class="token keyword">else</span> votes<span class="token operator">--</span><span class="token punctuation">;</span>      <span class="token punctuation">}</span>      <span class="token keyword">return</span> x<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">//优化</span><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">majorityElement</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> nums<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">int</span> x <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">,</span> votes <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>        <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> num <span class="token operator">:</span> nums<span class="token punctuation">)</span><span class="token punctuation">{</span>            <span class="token keyword">if</span><span class="token punctuation">(</span>votes <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> x <span class="token operator">=</span> num<span class="token punctuation">;</span>            votes <span class="token operator">+=</span> num <span class="token operator">==</span> x <span class="token operator">?</span> <span class="token number">1</span> <span class="token operator">:</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> x<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="复杂度-44"><a href="#复杂度-44" class="headerlink" title="复杂度"></a>复杂度</h3><h4 id="方法1-8"><a href="#方法1-8" class="headerlink" title="方法1"></a>方法1</h4><ul><li><strong>时间复杂度 O(N) ：</strong> 其中 N 为数组 nums 的长度；遍历数组占用 O(N) </li><li><strong>空间复杂度 O(N) ：</strong>  HashMap 使用 O(N) 额外空间</li></ul><h4 id="方法2-9"><a href="#方法2-9" class="headerlink" title="方法2"></a>方法2</h4><p>**时间复杂度 O(nlogn) **</p><h3 id="方法3-1"><a href="#方法3-1" class="headerlink" title="方法3"></a>方法3</h3><ul><li><strong>时间复杂度 O(N)：</strong> N 为数组 <code>nums</code> 长度。</li><li><strong>空间复杂度 O(1) ：</strong> <code>votes</code> 变量使用常数大小的额外空间。</li></ul><h2 id="66-构建乘积数组"><a href="#66-构建乘积数组" class="headerlink" title="66. 构建乘积数组"></a>66. 构建乘积数组</h2><h3 id="题目-55"><a href="#题目-55" class="headerlink" title="题目"></a>题目</h3><blockquote><p>给定一个数组 A[0,1,…,n-1]，请构建一个数组 B[0,1,…,n-1]，其中 B[i] 的值是数组 A 中除了下标 i 以外的元素的积, 即 B[i]&#x3D;A[0]×A[1]×…×A[i-1]×A[i+1]×…×A[n-1]。不能使用除法。</p><p>示例:</p><pre><code>输入: [1,2,3,4,5]输出: [120,60,40,30,24]</code></pre></blockquote><h3 id="解答-55"><a href="#解答-55" class="headerlink" title="解答"></a>解答</h3><p>根据表格的主对角线（全为 1 ），可将表格分为 <strong>上三角</strong> 和 <strong>下三角</strong> 两部分。分别迭代计算下三角和上三角两部分的乘积，即可 <strong>不使用除法</strong> 就获得结果。</p><p><img src="https://img.jwt1399.top/img/202209041308244.png"></p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token function">constructArr</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> a<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">int</span> len <span class="token operator">=</span> a<span class="token punctuation">.</span>length<span class="token punctuation">;</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>len <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">int</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">;</span>        <span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> b <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">int</span><span class="token punctuation">[</span>len<span class="token punctuation">]</span><span class="token punctuation">;</span>        b<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>        <span class="token keyword">int</span> tmp <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>        <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> len<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token comment" spellcheck="true">//计算下三角</span>            b<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> b<span class="token punctuation">[</span>i <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">*</span> a<span class="token punctuation">[</span>i <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> len <span class="token operator">-</span> <span class="token number">2</span><span class="token punctuation">;</span> i <span class="token operator">>=</span> <span class="token number">0</span><span class="token punctuation">;</span> i<span class="token operator">--</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token comment" spellcheck="true">//计算上三角</span>            tmp <span class="token operator">*=</span> a<span class="token punctuation">[</span>i <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">;</span>            b<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">*=</span> tmp<span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> b<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token function">constructArr</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> a<span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token keyword">int</span> n <span class="token operator">=</span> a<span class="token punctuation">.</span>length<span class="token punctuation">;</span>    <span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> B <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">int</span><span class="token punctuation">[</span>n<span class="token punctuation">]</span><span class="token punctuation">;</span>    <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">,</span> product <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> n<span class="token punctuation">;</span> product <span class="token operator">*=</span> a<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">,</span> i<span class="token operator">++</span><span class="token punctuation">)</span>       <span class="token comment" spellcheck="true">/* 从左往右累乘 */</span>        B<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> product<span class="token punctuation">;</span>    <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> n <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">,</span> product <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> i <span class="token operator">>=</span> <span class="token number">0</span><span class="token punctuation">;</span> product <span class="token operator">*=</span> a<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">,</span> i<span class="token operator">--</span><span class="token punctuation">)</span>  <span class="token comment" spellcheck="true">/* 从右往左累乘 */</span>        B<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">*=</span> product<span class="token punctuation">;</span>    <span class="token keyword">return</span> B<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="复杂度-45"><a href="#复杂度-45" class="headerlink" title="复杂度"></a>复杂度</h3><ul><li><strong>时间复杂度 O(N)：</strong> 其中 N 为数组长度，两轮遍历数组 a ，使用 O(N) 时间。</li><li><strong>空间复杂度 O(1)：</strong> 变量 tmp 使用常数大小额外空间（数组 b 作为返回值，不计入复杂度考虑）。</li></ul><h1 id="第-24-天-数学（中等）"><a href="#第-24-天-数学（中等）" class="headerlink" title="第 24 天 数学（中等）"></a>第 24 天 数学（中等）</h1><h2 id="14-I-剪绳子"><a href="#14-I-剪绳子" class="headerlink" title="14- I. 剪绳子"></a>14- I. 剪绳子</h2><h3 id="题目-56"><a href="#题目-56" class="headerlink" title="题目"></a>题目</h3><blockquote><p>给你一根长度为 n 的绳子，请把绳子剪成整数长度的 m 段（m、n都是整数，n&gt;1并且m&gt;1），每段绳子的长度记为 k[0],k[1]…k[m-1]。请问 k[0]*k[1]*…*k[m-1] 可能的最大乘积是多少？例如，当绳子的长度是8时，我们把它剪成长度分别为2、3、3的三段，此时得到的最大乘积是18。</p><p>示例 1：</p><pre><code>输入: 2输出: 1解释: 2 = 1 + 1, 1 × 1 = 1</code></pre><p>示例 2:</p><pre><code>输入: 10输出: 36解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36</code></pre></blockquote><h3 id="解答-56"><a href="#解答-56" class="headerlink" title="解答"></a>解答</h3><p><strong>数学推导：</strong></p><ul><li><p>① 当所有绳段长度相等时，乘积最大。</p></li><li><p>② 最优的绳段长度为 3 。</p></li></ul><h5 id="切分规则："><a href="#切分规则：" class="headerlink" title="切分规则："></a>切分规则：</h5><ol><li><strong>最优：</strong> 3 。把绳子尽可能切为多个长度为 3 的片段，留下的最后一段绳子的长度可能为 0,1,2 三种情况。</li><li><strong>次优：</strong> 2 。若最后一段绳子长度为 2 ；则保留，不再拆为 1+1 。</li><li><strong>最差：</strong> 1 。若最后一段绳子长度为 1 ；则应把一份 3+1 替换为 2+2，因为 2×2&gt;3×1</li></ol><p><img src="https://img.jwt1399.top/img/202209051336775.png"></p><h5 id="算法流程：-5"><a href="#算法流程：-5" class="headerlink" title="算法流程："></a>算法流程：</h5><ol><li>当 n ≤ 3 时，按照规则应不切分，但由于题目要求必须剪成 m&gt;1 段，因此必须剪出一段长度为 1 的绳子，即返回 n−1 。</li><li>当 n &gt; 3 时，求 n 除以 3 的 整数部分 a 和 余数部分 b （即 n=3a+b），并分为以下三种情况：<ul><li>当 b &#x3D; 0 时，直接返回 3<sup>a</sup>；</li><li>当 b &#x3D; 1 时，要将一个 1+3 转换为 2+2，因此返回 3<sup>a-1</sup>×4；</li><li>当 b=2 时，返回 3<sup>a</sup>×2。</li></ul></li></ol><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">cuttingRope</span><span class="token punctuation">(</span><span class="token keyword">int</span> n<span class="token punctuation">)</span> <span class="token punctuation">{</span>      <span class="token keyword">if</span><span class="token punctuation">(</span>n <span class="token operator">&lt;=</span> <span class="token number">3</span><span class="token punctuation">)</span> <span class="token keyword">return</span> n <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">;</span>      <span class="token keyword">int</span> a <span class="token operator">=</span> n <span class="token operator">/</span> <span class="token number">3</span><span class="token punctuation">;</span>      <span class="token keyword">int</span> b <span class="token operator">=</span> n <span class="token operator">%</span> <span class="token number">3</span><span class="token punctuation">;</span>      <span class="token keyword">if</span><span class="token punctuation">(</span>b <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">)</span>Math<span class="token punctuation">.</span><span class="token function">pow</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">,</span>a<span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token keyword">if</span><span class="token punctuation">(</span>b <span class="token operator">==</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">)</span>Math<span class="token punctuation">.</span><span class="token function">pow</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">,</span>a<span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token operator">*</span><span class="token number">4</span><span class="token punctuation">;</span>      <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">)</span>Math<span class="token punctuation">.</span><span class="token function">pow</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">,</span>a<span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token number">2</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="复杂度-46"><a href="#复杂度-46" class="headerlink" title="复杂度"></a>复杂度</h3><ul><li><strong>时间复杂度 O(1) ：</strong> 仅有求整、求余、次方运算。</li><li><strong>空间复杂度 O(1) ：</strong> 变量 <code>a</code> 和 <code>b</code> 使用常数大小额外空间。</li></ul><h2 id="57-II-和为s的连续正数序列"><a href="#57-II-和为s的连续正数序列" class="headerlink" title="57 - II. 和为s的连续正数序列"></a>57 - II. 和为s的连续正数序列</h2><h3 id="题目-57"><a href="#题目-57" class="headerlink" title="题目"></a>题目</h3><blockquote><p>输入一个正整数 target ，输出所有和为 target 的连续正整数序列（至少含有两个数）。</p><p>序列内的数字由小到大排列，不同序列按照首个数字从小到大排列。</p><p>示例 1：</p><pre><code>输入：target = 9输出：[[2,3,4],[4,5]]</code></pre><p>示例 2：</p><pre><code>输入：target = 15输出：[[1,2,3,4,5],[4,5,6],[7,8]]</code></pre></blockquote><h3 id="解答-57"><a href="#解答-57" class="headerlink" title="解答"></a>解答</h3><p>设连续正整数序列的<strong>左边界 i</strong> 和<strong>右边界 j</strong> ，则可构建滑动窗口从左向右滑动。循环中，每轮判断滑动窗口内元素和与目标值 target 的大小关系，若相等则记录结果，若大于 target 则移动左边界 i （以减小窗口内的元素和），若小于 target 则移动右边界 j （以增大窗口内的元素和）。</p><p><img src="https://img.jwt1399.top/img/202209051406993.png"></p><h5 id="算法流程：-6"><a href="#算法流程：-6" class="headerlink" title="算法流程："></a>算法流程：</h5><ol><li><p><strong>初始化：</strong> 左边界 i=1，右边界 j=2，元素和 s=3 ，结果列表 res ；</p></li><li><p><strong>循环：</strong> 当 i ≥ j 时跳出；</p><ul><li>当 s &gt; target 时： 更新元素和 s，并向右移动左边界 i=i+1 ；</li><li>当 s &lt; target 时： 向右移动右边界 j=j+1 ，并更新元素和 s ；</li><li>当 s &#x3D; target 时： 记录连续整数序列，并向右移动左边界 i=i+1，更新元素和 s ；</li></ul></li><li><p><strong>返回值：</strong> 返回结果列表 res ；</p></li></ol><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token function">findContinuousSequence</span><span class="token punctuation">(</span><span class="token keyword">int</span> target<span class="token punctuation">)</span> <span class="token punctuation">{</span>      <span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">,</span> j <span class="token operator">=</span> <span class="token number">2</span><span class="token punctuation">,</span> s <span class="token operator">=</span> <span class="token number">3</span><span class="token punctuation">;</span>      List<span class="token operator">&lt;</span><span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">></span> res <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ArrayList</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token keyword">while</span><span class="token punctuation">(</span>i <span class="token operator">&lt;</span> j<span class="token punctuation">)</span><span class="token punctuation">{</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>s <span class="token operator">></span> target<span class="token punctuation">)</span> <span class="token punctuation">{</span>          s <span class="token operator">-=</span> i<span class="token punctuation">;</span>          i<span class="token operator">++</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">else</span> <span class="token keyword">if</span><span class="token punctuation">(</span>s <span class="token operator">&lt;</span> target<span class="token punctuation">)</span><span class="token punctuation">{</span>            j<span class="token operator">++</span><span class="token punctuation">;</span>            s <span class="token operator">+=</span> j<span class="token punctuation">;</span>        <span class="token punctuation">}</span>         <span class="token keyword">else</span><span class="token punctuation">{</span>            <span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> ans <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">int</span><span class="token punctuation">[</span>j<span class="token operator">-</span>i<span class="token operator">+</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">;</span>            <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> k <span class="token operator">=</span> i<span class="token punctuation">;</span> k <span class="token operator">&lt;=</span> j<span class="token punctuation">;</span> k<span class="token operator">++</span><span class="token punctuation">)</span>                  ans<span class="token punctuation">[</span>k <span class="token operator">-</span> i<span class="token punctuation">]</span> <span class="token operator">=</span> k<span class="token punctuation">;</span>            res<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>ans<span class="token punctuation">)</span><span class="token punctuation">;</span>            s <span class="token operator">-=</span> i<span class="token punctuation">;</span>              i<span class="token operator">++</span><span class="token punctuation">;</span>                    <span class="token punctuation">}</span>      <span class="token punctuation">}</span>      <span class="token keyword">return</span> res<span class="token punctuation">.</span><span class="token function">toArray</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">int</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="复杂度-47"><a href="#复杂度-47" class="headerlink" title="复杂度"></a>复杂度</h3><ul><li><strong>时间复杂度 O(N) ：</strong> 其中 N=target；连续整数序列至少有两个数字，而 i&lt;j恒成立，因此至多循环 target 次；</li><li><strong>空间复杂度 O(1) ：</strong> 变量 i , j , s 使用常数大小的额外空间。</li></ul><h2 id="62-圆圈中最后剩下的数字"><a href="#62-圆圈中最后剩下的数字" class="headerlink" title="62.圆圈中最后剩下的数字"></a>62.圆圈中最后剩下的数字</h2><h3 id="题目-58"><a href="#题目-58" class="headerlink" title="题目"></a>题目</h3><blockquote><p>0,1,···,n-1这n个数字排成一个圆圈，从数字0开始，每次从这个圆圈里删除第m个数字（删除后从下一个数字开始计数）。求出这个圆圈里剩下的最后一个数字。</p><p>例如，0、1、2、3、4这5个数字组成一个圆圈，从数字0开始每次删除第3个数字，则删除的前4个数字依次是2、0、4、1，因此最后剩下的数字是3。</p><p>示例 1：</p><pre><code>输入: n = 5, m = 3输出: 3</code></pre><p>示例 2：</p><pre><code>输入: n = 10, m = 17输出: 2</code></pre></blockquote><h3 id="解答-58"><a href="#解答-58" class="headerlink" title="解答"></a>解答</h3><p>这个问题是以弗拉维奥·约瑟夫命名的，称为“<strong>约瑟夫环</strong>”问题，可使用 <strong>动态规划</strong> 解决。</p><p><strong>最容易想到的方法是：</strong>构建一个长度为 n 的链表，各节点值为对应的顺序索引；每轮删除第 m 个节点，直至链表长度为 1 时结束，返回最后剩余节点的值即可。模拟法需要循环删除 n−1 轮，每轮在链表中寻找删除节点需要 m 次访问操作（链表线性遍历），因此总体时间复杂度为 O(nm) 。根据题目给定的 m,n 取值范围，可知此时间复杂度是不可接受的。</p><p><strong>动态规划解法</strong></p><p>设 <code>f(n,m)</code> 表示在 n 个数字(0～n-1)中不断删除第 m 个数字最后剩下的数字。删除一个数字后剩下了 n-1 个数字，那么再次删除就是在 n-1 个数字中删除第 m 个数字，可以表示为 <code>f(n-1,m)</code>。</p><p>但实际上直接这么表示是不对的。因为<code>f(n,m)</code>表示的是从区间<code>[0~n-1]</code>不断进行删除操作，被删除的元素是<code>k=(m-1)%n</code>，而下一次删除时，起点是 <code>k+1（即m%n） </code>，区间不再满足上述要求，因此从第二步不断删除的结果可以表示为<code>f&#39;(n-1,m)</code>。则有：<code>f(n,m) = f&#39;(n-1,m)</code></p><p>现在就要想办法找到 <code>f&#39;(n-1,m)</code>和<code>f(n- 1, m)</code>之间的关系。则二者操作的区间表示如下:</p><pre><code>f&#39;(n-1,m)表示序列: k+1 k+2 k+3 ... n-1     0     1  ... k-1f(n-1,m) 表示序列:  0   1   2  ... n-k-2 n-k-1  n-k ... n-2</code></pre><p>由此推出 <code>f&#39;(n-1,m)=(f(n-1,m)+k+1)%n</code></p><p>化简：</p><pre><code>因为f&#39;(n-1,m)=(f(n-1,m)+k+1)%nk+1= m%nf(n,m) = f&#39;(n-1,m)所以f(n-1,m)=[f(n-1,m)+m]%n</code></pre><p>由此可知 f(n,m) 可由 f(n-1,m) 得到，f(n-1,m) 可由 f(n-2,m) 得到，……，f(2,m) 可由 f(1,m) 得到；因此，若给定 f(1,m) 的值，就可以递推至任意 f(n,m) 。而 f(1,m) &#x3D; 0 恒成立。得到如下递归关系：</p><p><img src="https://img.jwt1399.top/img/202209111343271.png"></p><h5 id="动态规划解析："><a href="#动态规划解析：" class="headerlink" title="动态规划解析："></a>动态规划解析：</h5><ol><li><p><strong>状态定义：</strong> 设「i,m问题」的解为 dp[i]；</p></li><li><p><strong>转移方程：</strong> 通过以下公式可从 dp[i−1] 递推得到 dp[i] = (dp[i−1] + m) % i；</p></li><li><p><strong>初始状态：</strong>「1,m 问题」的解恒为 0 ，即 dp[1] &#x3D; 0；</p></li><li><p><strong>返回值：</strong> 返回「n,m 问题」的解 dp[n] ；</p></li></ol><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">lastRemaining</span><span class="token punctuation">(</span><span class="token keyword">int</span> n<span class="token punctuation">,</span> <span class="token keyword">int</span> m<span class="token punctuation">)</span> <span class="token punctuation">{</span>      <span class="token keyword">int</span> dp<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">int</span><span class="token punctuation">[</span>n<span class="token operator">+</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">;</span>       dp<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>      <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">2</span><span class="token punctuation">;</span> i <span class="token operator">&lt;=</span> n<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span><span class="token punctuation">{</span>        dp<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">(</span>dp<span class="token punctuation">[</span>i<span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">+</span> m<span class="token punctuation">)</span> <span class="token operator">%</span> i<span class="token punctuation">;</span>      <span class="token punctuation">}</span>      <span class="token keyword">return</span> dp<span class="token punctuation">[</span>n<span class="token punctuation">]</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">//优化</span><span class="token comment" spellcheck="true">//根据状态转移方程的递推特性，无需建立状态列表 dp ，而使用一个变量 x 执行状态转移即可。</span><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">lastRemaining</span><span class="token punctuation">(</span><span class="token keyword">int</span> n<span class="token punctuation">,</span> <span class="token keyword">int</span> m<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">int</span> x <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>        <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">2</span><span class="token punctuation">;</span> i <span class="token operator">&lt;=</span> n<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            x <span class="token operator">=</span> <span class="token punctuation">(</span>x <span class="token operator">+</span> m<span class="token punctuation">)</span> <span class="token operator">%</span> i<span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> x<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="复杂度-48"><a href="#复杂度-48" class="headerlink" title="复杂度"></a>复杂度</h3><ul><li><strong>时间复杂度 O(n)：</strong> 状态转移循环 n−1次使用 O(n)时间，状态转移方程计算使用 O(1) 时间；</li><li><strong>空间复杂度 O(1)：</strong> 使用常数大小的额外空间；</li></ul><h1 id="第-25-天-模拟（中等）"><a href="#第-25-天-模拟（中等）" class="headerlink" title="第 25 天 模拟（中等）"></a>第 25 天 模拟（中等）</h1><h2 id="29-顺时针打印矩阵"><a href="#29-顺时针打印矩阵" class="headerlink" title="29. 顺时针打印矩阵"></a>29. 顺时针打印矩阵</h2><h3 id="题目-59"><a href="#题目-59" class="headerlink" title="题目"></a>题目</h3><blockquote><p>输入一个矩阵，按照从外向里以顺时针的顺序依次打印出每一个数字。</p><p>示例 1：</p><pre><code>输入：matrix = [[1,2,3],[4,5,6],[7,8,9]]输出：[1,2,3,6,9,8,7,4,5]</code></pre><p>示例 2：</p><pre><code>输入：matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]输出：[1,2,3,4,8,12,11,10,9,5,6,7]</code></pre></blockquote><h3 id="解答-59"><a href="#解答-59" class="headerlink" title="解答"></a>解答</h3><p>顺时针打印矩阵的顺序是 <strong>“从左向右、从上向下、从右向左、从下向上”</strong> 循环。</p><p><strong>算法流程：</strong></p><ol><li><strong>空值处理：</strong> 当 <code>matrix</code> 为空时，直接返回空列表 <code>[]</code> 即可。</li><li><strong>初始化：</strong> 矩阵 左、右、上、下 四个边界 <code>left</code> , <code>right</code> , <code>top</code> , <code>bottom</code> ，用于打印的结果列表 <code>res</code> 。</li><li><strong>循环打印：</strong> “从左向右、从上向下、从右向左、从下向上” 四个方向循环，每个方向打印中做以下三件事 <em>（各方向的具体信息见下表）</em> ；<ol><li>根据边界打印，即将元素按顺序添加至列表 <code>res</code> 尾部；</li><li>边界向内收缩 1 （代表已被打印）；</li><li>判断是否打印完毕（边界是否相遇），若打印完毕则跳出。</li></ol></li><li><strong>返回值：</strong> 返回 <code>res</code> 即可。</li></ol><table><thead><tr><th>打印方向</th><th>1. 根据边界打印</th><th>2. 边界向内收缩</th><th>3. 是否打印完毕</th></tr></thead><tbody><tr><td>从左向右</td><td>左边界<code>left</code> ，右边界 <code>right</code></td><td>上边界 <code>top</code> 加 1</td><td>是否 <code>top &gt; bottom</code></td></tr><tr><td>从上向下</td><td>上边界 <code>top</code> ，下边界<code>bottom</code></td><td>右边界 <code>right</code> 减 1</td><td>是否 <code>left &gt; right</code></td></tr><tr><td>从右向左</td><td>右边界 <code>right</code> ，左边界<code>left</code></td><td>下边界 <code>bottom</code> 减 1</td><td>是否 <code>top &gt; bottom</code></td></tr><tr><td>从下向上</td><td>下边界 <code>bottom</code> ，上边界<code>top</code></td><td>左边界 <code>left</code> 加 1</td><td>是否 <code>left &gt; right</code></td></tr></tbody></table><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token function">spiralOrder</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token punctuation">]</span> matrix<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>matrix<span class="token punctuation">.</span>length <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">int</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">;</span>        <span class="token keyword">int</span> left <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">,</span> right <span class="token operator">=</span> matrix<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span>length <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">,</span> top <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">,</span> bottom <span class="token operator">=</span> matrix<span class="token punctuation">.</span>length <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">,</span> count <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>        <span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> res <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">int</span><span class="token punctuation">[</span><span class="token punctuation">(</span>right <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token operator">*</span><span class="token punctuation">(</span>bottom <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">;</span>        <span class="token keyword">while</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">{</span>            <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> left<span class="token punctuation">;</span> i <span class="token operator">&lt;=</span> right<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span><span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// 从左到右</span>                res<span class="token punctuation">[</span>count<span class="token operator">++</span><span class="token punctuation">]</span> <span class="token operator">=</span> matrix<span class="token punctuation">[</span>top<span class="token punctuation">]</span><span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>            <span class="token keyword">if</span><span class="token punctuation">(</span><span class="token operator">++</span>top <span class="token operator">></span> bottom<span class="token punctuation">)</span> <span class="token keyword">break</span><span class="token punctuation">;</span>            <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> top<span class="token punctuation">;</span> i <span class="token operator">&lt;=</span> bottom<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span><span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// 从上到下</span>                res<span class="token punctuation">[</span>count<span class="token operator">++</span><span class="token punctuation">]</span> <span class="token operator">=</span> matrix<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span>right<span class="token punctuation">]</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>            <span class="token keyword">if</span><span class="token punctuation">(</span>left <span class="token operator">></span> <span class="token operator">--</span>right<span class="token punctuation">)</span> <span class="token keyword">break</span><span class="token punctuation">;</span>            <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> right<span class="token punctuation">;</span> i <span class="token operator">>=</span> left<span class="token punctuation">;</span> i<span class="token operator">--</span><span class="token punctuation">)</span><span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// 从右到左</span>                res<span class="token punctuation">[</span>count<span class="token operator">++</span><span class="token punctuation">]</span> <span class="token operator">=</span> matrix<span class="token punctuation">[</span>bottom<span class="token punctuation">]</span><span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>            <span class="token keyword">if</span><span class="token punctuation">(</span>top <span class="token operator">></span> <span class="token operator">--</span>bottom<span class="token punctuation">)</span> <span class="token keyword">break</span><span class="token punctuation">;</span>            <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> bottom<span class="token punctuation">;</span> i <span class="token operator">>=</span> top<span class="token punctuation">;</span> i<span class="token operator">--</span><span class="token punctuation">)</span><span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// 从下到上</span>                res<span class="token punctuation">[</span>count<span class="token operator">++</span><span class="token punctuation">]</span> <span class="token operator">=</span> matrix<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span>left<span class="token punctuation">]</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>            <span class="token keyword">if</span><span class="token punctuation">(</span><span class="token operator">++</span>left <span class="token operator">></span> right<span class="token punctuation">)</span> <span class="token keyword">break</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> res<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="复杂度-49"><a href="#复杂度-49" class="headerlink" title="复杂度"></a>复杂度</h3><ul><li><strong>时间复杂度 O(MN)：</strong> M,N 分别为矩阵行数和列数。</li><li><strong>空间复杂度 O(1) ：</strong> 四个边界使用常数大小的 <strong>额外</strong> 空间（ <code>res</code> 为必须使用的空间）。</li></ul><h2 id="31-栈的压入、弹出序列"><a href="#31-栈的压入、弹出序列" class="headerlink" title="31. 栈的压入、弹出序列"></a>31. 栈的压入、弹出序列</h2><h3 id="题目-60"><a href="#题目-60" class="headerlink" title="题目"></a>题目</h3><blockquote><p>输入两个整数序列，第一个序列表示栈的压入顺序，请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如，序列 {1,2,3,4,5} 是某栈的压栈序列，序列 {4,5,3,2,1} 是该压栈序列对应的一个弹出序列，但 {4,3,5,1,2} 就不可能是该压栈序列的弹出序列。</p><p>示例 1：</p><pre><code>输入：pushed = [1,2,3,4,5], popped = [4,5,3,2,1]输出：true解释：我们可以按以下顺序执行：push(1), push(2), push(3), push(4), pop() -&gt; 4,push(5), pop() -&gt; 5, pop() -&gt; 3, pop() -&gt; 2, pop() -&gt; 1</code></pre><p>示例 2：</p><pre><code>输入：pushed = [1,2,3,4,5], popped = [4,3,5,1,2]输出：false解释：1 不能在 2 之前弹出。</code></pre></blockquote><h3 id="解答-60"><a href="#解答-60" class="headerlink" title="解答"></a>解答</h3><p>考虑借用一个辅助栈 stack ，<strong>模拟</strong> 压入 &#x2F; 弹出操作的排列。根据是否模拟成功，即可得到结果。</p><ul><li><strong>入栈操作：</strong> 按照压栈序列的顺序执行。</li><li><strong>出栈操作：</strong> 每次入栈后，循环判断 “<strong>栈顶元素 === 弹出序列的当前元素</strong>” 是否成立，将符合弹出序列顺序的栈顶元素全部弹出。</li></ul><p><strong>算法流程：</strong></p><ol><li><strong>初始化：</strong> 辅助栈 stack，弹出序列的索引 i ；</li><li><strong>遍历压栈序列：</strong> 各元素记为 num ；<ol><li>元素 num 入栈；</li><li>循环出栈：若 stack 的栈顶元素 === 弹出序列元素 popped[i] ，则执行出栈与 i++ ；</li></ol></li><li><strong>返回值：</strong> 若 stack 为空，则此弹出序列合法。</li></ol><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">boolean</span> <span class="token function">validateStackSequences</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> pushed<span class="token punctuation">,</span> <span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> popped<span class="token punctuation">)</span> <span class="token punctuation">{</span>        Stack<span class="token operator">&lt;</span>Integer<span class="token operator">></span> stack <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Stack</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>        <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> num <span class="token operator">:</span> pushed<span class="token punctuation">)</span> <span class="token punctuation">{</span>            stack<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>num<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// num 入栈</span>            <span class="token keyword">while</span><span class="token punctuation">(</span><span class="token operator">!</span>stack<span class="token punctuation">.</span><span class="token function">isEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> stack<span class="token punctuation">.</span><span class="token function">peek</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">==</span> popped<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// 循环判断与出栈</span>                stack<span class="token punctuation">.</span><span class="token function">pop</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                i<span class="token operator">++</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> stack<span class="token punctuation">.</span><span class="token function">isEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="复杂度-50"><a href="#复杂度-50" class="headerlink" title="复杂度"></a>复杂度</h3><ul><li><strong>时间复杂度 O(N) ：</strong> 其中 N 为列表 pushed 的长度；每个元素最多入栈与出栈一次，即最多共 2N 次出入栈操作。</li><li><strong>空间复杂度 O(N) ：</strong> 辅助栈 stack 最多同时存储 N 个元素。</li></ul><h1 id="第-26-天-字符串（中等）"><a href="#第-26-天-字符串（中等）" class="headerlink" title="第 26 天 字符串（中等）"></a>第 26 天 字符串（中等）</h1><h2 id="20-表示数值的字符串"><a href="#20-表示数值的字符串" class="headerlink" title="20. 表示数值的字符串"></a>20. 表示数值的字符串</h2><h3 id="题目-61"><a href="#题目-61" class="headerlink" title="题目"></a>题目</h3><blockquote><p>请实现一个函数用来判断字符串是否表示<strong>数值</strong>（包括整数和小数）。</p><p><strong>数值</strong>（按顺序）可以分成以下几个部分：</p><ol><li>若干空格</li><li>一个 <strong>小数</strong> 或者 <strong>整数</strong></li><li>（可选）一个 <code>&#39;e&#39;</code> 或 <code>&#39;E&#39;</code> ，后面跟着一个 <strong>整数</strong></li><li>若干空格</li></ol><p><strong>小数</strong>（按顺序）可以分成以下几个部分：</p><ol><li>（可选）一个符号字符（<code>&#39;+&#39;</code> 或 <code>&#39;-&#39;</code>）</li><li>下述格式之一：</li><li>至少一位数字，后面跟着一个点 <code>&#39;.&#39;</code></li><li>至少一位数字，后面跟着一个点 <code>&#39;.&#39;</code> ，后面再跟着至少一位数字</li><li>一个点 <code>&#39;.&#39;</code> ，后面跟着至少一位数字</li></ol><p><strong>整数</strong>（按顺序）可以分成以下几个部分：</p><ol><li>（可选）一个符号字符（<code>&#39;+&#39;</code> 或 <code>&#39;-&#39;</code>）</li><li>至少一位数字</li></ol><p>部分<strong>数值</strong>列举如下：</p><ul><li><code>[&quot;+100&quot;, &quot;5e2&quot;, &quot;-123&quot;, &quot;3.1416&quot;, &quot;-1E-16&quot;, &quot;0123&quot;]</code></li></ul><p>部分<strong>非数值</strong>列举如下：</p><ul><li><code>[&quot;12e&quot;, &quot;1a3.14&quot;, &quot;1.2.3&quot;, &quot;+-5&quot;, &quot;12e+5.4&quot;]</code></li></ul><p>示例 1：</p><pre><code>输入：s = &quot;0&quot;输出：true</code></pre><p>示例 2：</p><pre><code>输入：s = &quot;e&quot;输出：false</code></pre><p>示例 3：</p><pre><code>输入：s = &quot;.&quot;输出：false</code></pre><p>示例 4：</p><pre><code>输入：s = &quot;    .1  &quot;输出：true</code></pre></blockquote><h3 id="解答-61"><a href="#解答-61" class="headerlink" title="解答"></a>解答</h3><h4 id="方法1：暴力"><a href="#方法1：暴力" class="headerlink" title="方法1：暴力"></a>方法1：暴力</h4><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">boolean</span> <span class="token function">isNumber</span><span class="token punctuation">(</span>String s<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>s <span class="token operator">==</span> null <span class="token operator">||</span> s<span class="token punctuation">.</span><span class="token function">length</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// s为空对象或s长度为0,不表示数值</span>        <span class="token comment" spellcheck="true">//是否出现数字、小数点、e或者E</span>        <span class="token keyword">boolean</span> numFlag <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">,</span> dotFlag <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">,</span> eFlag <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">//去掉首尾空格</span>        s <span class="token operator">=</span> s<span class="token punctuation">.</span><span class="token function">trim</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> s<span class="token punctuation">.</span><span class="token function">length</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">//判定为数字，则标记numFlag</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span>s<span class="token punctuation">.</span><span class="token function">charAt</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span> <span class="token operator">>=</span> <span class="token string">'0'</span> <span class="token operator">&amp;&amp;</span> s<span class="token punctuation">.</span><span class="token function">charAt</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span> <span class="token operator">&lt;=</span> <span class="token string">'9'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                numFlag <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>                <span class="token comment" spellcheck="true">//小数点只可以出现再e之前，且只能出现一次.num  num.num num.都是被允许的</span>            <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>s<span class="token punctuation">.</span><span class="token function">charAt</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token string">'.'</span> <span class="token operator">&amp;&amp;</span> <span class="token operator">!</span>dotFlag <span class="token operator">&amp;&amp;</span> <span class="token operator">!</span>eFlag<span class="token punctuation">)</span> <span class="token punctuation">{</span>                dotFlag <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>                <span class="token comment" spellcheck="true">//判定为e，需要没出现过e，并且出过数字了</span>            <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>s<span class="token punctuation">.</span><span class="token function">charAt</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token string">'e'</span> <span class="token operator">||</span> s<span class="token punctuation">.</span><span class="token function">charAt</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token string">'E'</span><span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> <span class="token operator">!</span>eFlag <span class="token operator">&amp;&amp;</span> numFlag<span class="token punctuation">)</span> <span class="token punctuation">{</span>                eFlag <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>                numFlag <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//重置，避免e以后没有出现数字，防止出现123e或者123e+的非法情况</span>                <span class="token comment" spellcheck="true">//判定为+-符号，只能出现在第一位或者紧接e后面</span>            <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>s<span class="token punctuation">.</span><span class="token function">charAt</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token string">'+'</span> <span class="token operator">||</span> s<span class="token punctuation">.</span><span class="token function">charAt</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token string">'-'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token keyword">if</span><span class="token punctuation">(</span>i <span class="token operator">!=</span> <span class="token number">0</span> <span class="token operator">&amp;&amp;</span> s<span class="token punctuation">.</span><span class="token function">charAt</span><span class="token punctuation">(</span>i <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">!=</span> <span class="token string">'e'</span> <span class="token operator">&amp;&amp;</span> s<span class="token punctuation">.</span><span class="token function">charAt</span><span class="token punctuation">(</span>i <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">!=</span> <span class="token string">'E'</span><span class="token punctuation">)</span>                   <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span>             <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//其他情况，都是非法的</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">//是否出现了数字 </span>        <span class="token keyword">return</span> numFlag<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="复杂度-51"><a href="#复杂度-51" class="headerlink" title="复杂度"></a>复杂度</h3><ul><li>时间复杂度：O(n)</li><li>空间复杂度：O(1)</li></ul><h2 id="67-把字符串转换成整数"><a href="#67-把字符串转换成整数" class="headerlink" title="67. 把字符串转换成整数"></a>67. 把字符串转换成整数</h2><h3 id="题目-62"><a href="#题目-62" class="headerlink" title="题目"></a>题目</h3><blockquote><p>写一个函数 StrToInt，实现把字符串转换成整数这个功能。不能使用 atoi 或者其他类似的库函数。</p><p>首先，该函数会根据需要丢弃无用的开头空格字符，直到寻找到第一个非空格的字符为止。</p><p>当我们寻找到的第一个非空字符为正或者负号时，则将该符号与之后面尽可能多的连续数字组合起来，作为该整数的正负号；假如第一个非空字符是数字，则直接将其与之后连续的数字字符组合起来，形成整数。</p><p>该字符串除了有效的整数部分之后也可能会存在多余的字符，这些字符可以被忽略，它们对于函数不应该造成影响。</p><p>注意：假如该字符串中的第一个非空格字符不是一个有效整数字符、字符串为空或字符串仅包含空白字符时，则你的函数不需要进行转换。</p><p>在任何情况下，若函数不能进行有效的转换时，请返回 0。</p><p><strong>说明：</strong></p><p>假设我们的环境只能存储 32 位大小的有符号整数，那么其数值范围为 [−2<sup>31</sup>,  2<sup>31</sup> − 1]。如果数值超过这个范围，请返回  INT_MAX (2<sup>31</sup> − 1) 或 INT_MIN (−2<sup>31</sup>) 。</p><p><strong>示例 1:</strong></p><pre><code>输入: &quot;42&quot;输出: 42</code></pre><p><strong>示例 2:</strong></p><pre><code>输入: &quot;   -42&quot;输出: -42解释: 第一个非空白字符为 &#39;-&#39;, 它是一个负号。    我们尽可能将负号与后面所有连续出现的数字组合起来，最后得到 -42 。</code></pre><p><strong>示例 3:</strong></p><pre><code>输入: &quot;4193 with words&quot;输出: 4193解释: 转换截止于数字 &#39;3&#39; ，因为它的下一个字符不为数字。</code></pre><p><strong>示例 4:</strong></p><pre><code>输入: &quot;words and 987&quot;输出: 0解释: 第一个非空字符是 &#39;w&#39;, 但它不是数字或正、负号。因此无法执行有效的转换。</code></pre><p><strong>示例 5:</strong></p><pre><code>输入: &quot;-91283472332&quot;输出: -2147483648解释: 数字 &quot;-91283472332&quot; 超过 32 位有符号整数范围。 因此返回 INT_MIN (−2^31) 。</code></pre></blockquote><h3 id="解答-62"><a href="#解答-62" class="headerlink" title="解答"></a>解答</h3><p><img src="https://img.jwt1399.top/img/202209071354147.png"></p><p><img src="https://img.jwt1399.top/img/202209071404320.png"></p><p>需要保证本次循环的 <code>res * 10 + chars[j]</code> 不超过 int 即可保证不越界</p><ul><li><code>res &gt; bndry</code> 意思是，此时 res 已经大于 bndry 了，* 10 一定越界</li><li><code>res == bndry &amp;&amp; chars[j] &gt; &#39;7&#39;</code> 的意思是，当 res &#x3D;&#x3D; bndry 时，即：214748364 此时 res * 10 变成 2147483640 此时没越界，但是还需要 + chars[j]，而 int 最大值为 2147483647，所以当chars[j] &gt; 7 时会越界</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>        <span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">strToInt</span><span class="token punctuation">(</span>String str<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">//去前后空格</span>            <span class="token keyword">char</span><span class="token punctuation">[</span><span class="token punctuation">]</span> chars <span class="token operator">=</span> str<span class="token punctuation">.</span><span class="token function">trim</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toCharArray</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span>chars<span class="token punctuation">.</span>length <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">//记录第一个符合是否为负数</span>            <span class="token keyword">int</span> sign <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">//开始遍历的位置</span>            <span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">//如果首个非空格字符为负号，那么从位置1开始遍历字符串，并且结果需要变成负数</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span>chars<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">==</span> <span class="token string">'-'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                sign <span class="token operator">=</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>chars<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">!=</span> <span class="token string">'+'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">//如果首个非空格字符不是负号也不是加号，从第一个元素开始遍历</span>                i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>            <span class="token keyword">int</span> bndry <span class="token operator">=</span> Integer<span class="token punctuation">.</span>MAX_VALUE <span class="token operator">/</span> <span class="token number">10</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 214748364</span>            <span class="token keyword">int</span> res <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>            <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> j <span class="token operator">=</span> i<span class="token punctuation">;</span> j <span class="token operator">&lt;</span> chars<span class="token punctuation">.</span>length<span class="token punctuation">;</span> j<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token comment" spellcheck="true">//遇到非数字直接退出</span>                <span class="token keyword">if</span> <span class="token punctuation">(</span>chars<span class="token punctuation">[</span>j<span class="token punctuation">]</span> <span class="token operator">></span> <span class="token string">'9'</span> <span class="token operator">||</span> chars<span class="token punctuation">[</span>j<span class="token punctuation">]</span> <span class="token operator">&lt;</span> <span class="token string">'0'</span><span class="token punctuation">)</span> <span class="token keyword">break</span><span class="token punctuation">;</span>                <span class="token comment" spellcheck="true">//判断是否越界 2^31−1 = 2147483647</span>                <span class="token keyword">if</span> <span class="token punctuation">(</span>res <span class="token operator">></span> bndry <span class="token operator">||</span> <span class="token punctuation">(</span>res <span class="token operator">==</span> bndry <span class="token operator">&amp;&amp;</span> chars<span class="token punctuation">[</span>j<span class="token punctuation">]</span> <span class="token operator">></span> <span class="token string">'7'</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                    <span class="token comment" spellcheck="true">//根据字符串首负号判断返回最大值还是最小值</span>                    <span class="token keyword">return</span> sign <span class="token operator">==</span> <span class="token number">1</span> <span class="token operator">?</span> Integer<span class="token punctuation">.</span>MAX_VALUE <span class="token operator">:</span> Integer<span class="token punctuation">.</span>MIN_VALUE<span class="token punctuation">;</span>                <span class="token punctuation">}</span>                <span class="token comment" spellcheck="true">//字符转数字：“此数字的ASCII码”与“0的ASCII码”相减即可</span>                res <span class="token operator">=</span> res <span class="token operator">*</span> <span class="token number">10</span> <span class="token operator">+</span> <span class="token punctuation">(</span>chars<span class="token punctuation">[</span>j<span class="token punctuation">]</span> <span class="token operator">-</span> <span class="token string">'0'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>            <span class="token comment" spellcheck="true">//返回结果，需要判断正负</span>            <span class="token keyword">return</span> res <span class="token operator">*</span> sign<span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="复杂度-52"><a href="#复杂度-52" class="headerlink" title="复杂度"></a>复杂度</h3><ul><li><strong>时间复杂度 O(N) ：</strong> 其中 N 为字符串长度，线性遍历字符串占用 O(N) 时间。</li><li><strong>空间复杂度 O(N) ：</strong> 删除首尾空格后需建立新字符串，最差情况下占用 O(N) 额外空间。</li></ul><h1 id="第-27-天-栈与队列（困难）"><a href="#第-27-天-栈与队列（困难）" class="headerlink" title="第 27 天 栈与队列（困难）"></a>第 27 天 栈与队列（困难）</h1><h2 id="59-I-滑动窗口的最大值"><a href="#59-I-滑动窗口的最大值" class="headerlink" title="59 - I. 滑动窗口的最大值"></a>59 - I. 滑动窗口的最大值</h2><h3 id="题目-63"><a href="#题目-63" class="headerlink" title="题目"></a>题目</h3><blockquote><p>给定一个数组 <code>nums</code> 和滑动窗口的大小 <code>k</code>，请找出所有滑动窗口里的最大值。</p><p><strong>示例:</strong></p><pre><code>输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3输出: [3,3,5,5,6,7] 解释:  滑动窗口的位置                最大值---------------               -----[1  3  -1] -3  5  3  6  7       31 [3  -1  -3] 5  3  6  7       31  3 [-1  -3  5] 3  6  7       51  3  -1 [-3  5  3] 6  7       51  3  -1  -3 [5  3  6] 7       61  3  -1  -3  5 [3  6  7]      7</code></pre></blockquote><h3 id="解答-63"><a href="#解答-63" class="headerlink" title="解答"></a>解答</h3><p>窗口对应的数据结构为 <strong>双端队列</strong> ，本题使用 <strong>单调队列</strong> 解决问题。遍历数组时，每轮保证单调队列 deque：</p><ol><li>deque 内 <strong>仅包含窗口内的元素</strong> ⇒ 每轮窗口滑动移除了元素 nums[i−1]，需将 deque 内的对应元素一起删除。</li><li>deque 内的元素 <strong>非严格递减</strong> ⇒ 每轮窗口滑动添加了元素 nums[j+1] ，需将 deque 内所有 &lt;nums[j+1]的元素删除。</li></ol><h5 id="算法流程：-7"><a href="#算法流程：-7" class="headerlink" title="算法流程："></a>算法流程：</h5><ol><li><strong>初始化：</strong> 双端队列 deque，结果列表 res ，数组长度 n ；</li><li><strong>滑动窗口：</strong> 左边界范围 i∈[1−k,n−k]，右边界范围 j∈[0,n−1] ；<ol><li>若 i&gt;0 且 队首元素 deque[0] &#x3D; 被删除元素 nums[i−1] ，则队首元素出队；</li><li>删除 deque 内所有 &lt;nums[j] 的元素，以保持 deque 递减；</li><li>将 nums[j] 添加至 deque 尾部；</li><li>若已形成窗口（即 i≥0 ）：将窗口最大值（即队首元素 deque[0]）添加至列表 res ；</li></ol></li><li><strong>返回值：</strong> 返回结果列表 res ；</li></ol><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token function">maxSlidingWindow</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> nums<span class="token punctuation">,</span> <span class="token keyword">int</span> k<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>nums<span class="token punctuation">.</span>length <span class="token operator">==</span> <span class="token number">0</span> <span class="token operator">||</span> k <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">int</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">;</span>        Deque<span class="token operator">&lt;</span>Integer<span class="token operator">></span> deque <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">LinkedList</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> res <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">int</span><span class="token punctuation">[</span>nums<span class="token punctuation">.</span>length <span class="token operator">-</span> k <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">;</span>        <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> j <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">,</span> i <span class="token operator">=</span> <span class="token number">1</span> <span class="token operator">-</span> k<span class="token punctuation">;</span> j <span class="token operator">&lt;</span> nums<span class="token punctuation">.</span>length<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">,</span> j<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 删除 deque 中对应的 nums[i-1]</span>            <span class="token keyword">if</span><span class="token punctuation">(</span>i <span class="token operator">></span> <span class="token number">0</span> <span class="token operator">&amp;&amp;</span> deque<span class="token punctuation">.</span><span class="token function">peekFirst</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">==</span> nums<span class="token punctuation">[</span>i <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">)</span>                deque<span class="token punctuation">.</span><span class="token function">removeFirst</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">// 保持 deque 递减</span>            <span class="token keyword">while</span><span class="token punctuation">(</span><span class="token operator">!</span>deque<span class="token punctuation">.</span><span class="token function">isEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> deque<span class="token punctuation">.</span><span class="token function">peekLast</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">&lt;</span> nums<span class="token punctuation">[</span>j<span class="token punctuation">]</span><span class="token punctuation">)</span>                deque<span class="token punctuation">.</span><span class="token function">removeLast</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            deque<span class="token punctuation">.</span><span class="token function">addLast</span><span class="token punctuation">(</span>nums<span class="token punctuation">[</span>j<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">// 记录窗口最大值</span>            <span class="token keyword">if</span><span class="token punctuation">(</span>i <span class="token operator">>=</span> <span class="token number">0</span><span class="token punctuation">)</span>                res<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> deque<span class="token punctuation">.</span><span class="token function">peekFirst</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> res<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="复杂度-53"><a href="#复杂度-53" class="headerlink" title="复杂度"></a>复杂度</h3><ul><li><strong>时间复杂度 O(n) ：</strong> 其中 n 为数组 nums 长度；线性遍历 nums 占用 O(n) ；每个元素最多仅入队和出队一次，因此单调队列 deque 占用 O(2n)。</li><li><strong>空间复杂度 O(k)：</strong> 双端队列 deque 中最多同时存储 k 个元素（即窗口大小）。</li></ul><h2 id="59-II-队列的最大值"><a href="#59-II-队列的最大值" class="headerlink" title="59 - II. 队列的最大值"></a>59 - II. 队列的最大值</h2><h3 id="题目-64"><a href="#题目-64" class="headerlink" title="题目"></a>题目</h3><blockquote><p>请定义一个队列并实现函数 <code>max_value</code> 得到队列里的最大值，要求函数<code>max_value</code>、<code>push_back</code> 和 <code>pop_front</code> 的<strong>均摊</strong>时间复杂度都是O(1)。</p><p>若队列为空，<code>pop_front</code> 和 <code>max_value</code> 需要返回 -1</p><p><strong>示例 1：</strong></p><pre><code>输入: [&quot;MaxQueue&quot;,&quot;push_back&quot;,&quot;push_back&quot;,&quot;max_value&quot;,&quot;pop_front&quot;,&quot;max_value&quot;][[],[1],[2],[],[],[]]输出: [null,null,null,2,1,2]</code></pre><p><strong>示例 2：</strong></p><pre><code>输入: [&quot;MaxQueue&quot;,&quot;pop_front&quot;,&quot;max_value&quot;][[],[],[]]输出: [null,-1,-1]</code></pre></blockquote><h3 id="解答-64"><a href="#解答-64" class="headerlink" title="解答"></a>解答</h3><p>考虑构建一个递减列表来保存队列所有递减的元素 ，递减队列随着入队和出队操作实时更新，这样队列最大元素就始终对应递减列表的首元素，实现了获取最大值 O(1)时间复杂度。</p><p><img src="https://img.jwt1399.top/img/202209082127707.png"></p><p>为了实现此递减列表，需要使用 <strong>双向队列</strong> ，假设队列已经有若干元素：</p><ol><li>当执行入队 <code>push_back()</code> 时： 若入队一个比队列某些元素更大的数字 x ，则为了保持此列表递减，需要将双向队列 <strong>尾部所有小于 x 的元素</strong> 弹出。</li><li>当执行出队 <code>pop_front()</code> 时： 若出队的元素是最大元素，则 双向队列 需要同时 <strong>将首元素出队</strong> ，以保持队列和双向队列的元素一致性。</li></ol><h5 id="函数设计："><a href="#函数设计：" class="headerlink" title="函数设计："></a>函数设计：</h5><p>初始化队列 <code>queue</code> ，双向队列 <code>deque</code> ；</p><p><strong>最大值 <code>max_value()</code> ：</strong></p><ul><li>当双向队列 <code>deque</code> 为空，则返回 −1 ；</li><li>否则，返回 <code>deque</code> 首元素；</li></ul><p><strong>入队 <code>push_back()</code> ：</strong></p><ol><li>将元素 <code>value</code> 入队 <code>queue</code> ；</li><li>将双向队列中队尾 <strong>所有</strong> 小于 <code>value</code> 的元素弹出（以保持 <code>deque</code> 非单调递减），并将元素 <code>value</code> 入队 <code>deque</code> ；</li></ol><p><strong>出队 <code>pop_front()</code> ：</strong></p><ol><li>若队列 <code>queue</code> 为空，则直接返回 −1 ；</li><li>否则，将 <code>queue</code> 首元素出队；</li><li>若 <code>deque</code> 首元素和 <code>queue</code> 首元素 <strong>相等</strong> ，则将 <code>deque</code> 首元素出队（以保持两队列 <strong>元素一致</strong> ） ；</li></ol><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">MaxQueue</span> <span class="token punctuation">{</span>    Queue<span class="token operator">&lt;</span>Integer<span class="token operator">></span> queue<span class="token punctuation">;</span>    Deque<span class="token operator">&lt;</span>Integer<span class="token operator">></span> deque<span class="token punctuation">;</span>    <span class="token keyword">public</span> <span class="token function">MaxQueue</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>      queue <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">LinkedList</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      deque <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">LinkedList</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>        <span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">max_value</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>      <span class="token keyword">return</span> deque<span class="token punctuation">.</span><span class="token function">isEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token operator">-</span><span class="token number">1</span> <span class="token operator">:</span> deque<span class="token punctuation">.</span><span class="token function">peekFirst</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">//入队</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">push_back</span><span class="token punctuation">(</span><span class="token keyword">int</span> value<span class="token punctuation">)</span> <span class="token punctuation">{</span>      queue<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token comment" spellcheck="true">// queue.offer(value);</span>      <span class="token keyword">while</span><span class="token punctuation">(</span><span class="token operator">!</span>deque<span class="token punctuation">.</span><span class="token function">isEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> deque<span class="token punctuation">.</span><span class="token function">peekLast</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">&lt;</span> value<span class="token punctuation">)</span>        deque<span class="token punctuation">.</span><span class="token function">removeLast</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// deque.pollLast();</span>      deque<span class="token punctuation">.</span><span class="token function">addLast</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// deque.offerLast(value);</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">//出队</span>    <span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">pop_front</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>      <span class="token keyword">if</span><span class="token punctuation">(</span>queue<span class="token punctuation">.</span><span class="token function">isEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span>      <span class="token comment" spellcheck="true">// queue 里面保存的是 Integer 而非 int ，peek() 返回的是 Integer 类型，没有自动拆箱，因此需要用 equals() 来比~</span>      <span class="token keyword">if</span><span class="token punctuation">(</span>queue<span class="token punctuation">.</span><span class="token function">peek</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span>deque<span class="token punctuation">.</span><span class="token function">peekFirst</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span>          deque<span class="token punctuation">.</span><span class="token function">removeFirst</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// deque.pollFirst();</span>      <span class="token keyword">return</span> queue<span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// queue.poll();</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">/** * Your MaxQueue object will be instantiated and called as such: * MaxQueue obj = new MaxQueue(); * int param_1 = obj.max_value(); * obj.push_back(value); * int param_3 = obj.pop_front(); */</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="复杂度-54"><a href="#复杂度-54" class="headerlink" title="复杂度"></a>复杂度</h3><ul><li><strong>时间复杂度 O(1) ：</strong> <code>max_value()</code>, <code>push_back()</code>, <code>pop_front()</code> 方法的均摊时间复杂度均为 O(1) ；</li><li><strong>空间复杂度 O(N) ：</strong> 当元素个数为 N 时，最差情况下<code>deque</code> 中保存 N 个元素，使用 O(N) 的额外空间；</li></ul><h1 id="第-28-天-搜索与回溯算法（困难）"><a href="#第-28-天-搜索与回溯算法（困难）" class="headerlink" title="第 28 天 搜索与回溯算法（困难）"></a>第 28 天 搜索与回溯算法（困难）</h1><h2 id="37-序列化二叉树"><a href="#37-序列化二叉树" class="headerlink" title="37. 序列化二叉树"></a>37. 序列化二叉树</h2><h3 id="题目-65"><a href="#题目-65" class="headerlink" title="题目"></a>题目</h3><blockquote><p>请实现两个函数，分别用来序列化和反序列化二叉树。</p><p>你需要设计一个算法来实现二叉树的序列化与反序列化。这里不限定你的序列 &#x2F; 反序列化算法执行逻辑，你只需要保证一个二叉树可以被序列化为一个字符串并且将这个字符串反序列化为原始的树结构。</p><p><strong>提示：</strong>输入输出格式与 LeetCode 目前使用的方式一致，详情请参阅 <a href="https://support.leetcode-cn.com/hc/kb/article/1567641/">LeetCode 序列化二叉树的格式</a>。你并非必须采取这种方式，你也可以采用其他的方法解决这个问题。</p><p><strong>示例：</strong></p><p><img src="https://img.jwt1399.top/img/202209091241895.jpg"></p><pre><code>输入：root = [1,2,3,null,null,4,5]输出：[1,2,3,null,null,4,5]</code></pre></blockquote><h3 id="解答-65"><a href="#解答-65" class="headerlink" title="解答"></a>解答</h3><h4 id="方法1：DFS"><a href="#方法1：DFS" class="headerlink" title="方法1：DFS"></a>方法1：DFS</h4><ul><li><strong>序列化</strong></li></ul><ol><li>递归的第一步都是特例的处理，因为这是递归的中止条件：如果根节点为空，返回”null“</li><li>序列化的结果为：根节点值 + “,” + 左子节点值(进入递归) + “,” + 右子节点值(进入递归)</li><li>递归就是不断将“根节点”值加到结果中的过程</li></ol><ul><li><strong>反序列化</strong></li></ul><ol><li>先将字符串转换成队列</li><li>接下来就进入了递归<br>i. 弹出左侧元素，即队列出队<br>ii. 如果元素为“null”，返回null<br>iii. 否则，新建一个节点，其值为弹出元素<br>iv. 其左子节点为队列的下一个元素，进入递归；右子节点为队列的下下个元素，也进入递归<br>v. 递归就是不断将子树的根节点连接到父节点的过程</li></ol><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Codec</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// Encodes a tree to a single string.</span>  <span class="token keyword">public</span> String <span class="token function">serialize</span><span class="token punctuation">(</span>TreeNode root<span class="token punctuation">)</span> <span class="token punctuation">{</span>      <span class="token keyword">if</span><span class="token punctuation">(</span>root <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token string">"null,"</span><span class="token punctuation">;</span>      String res <span class="token operator">=</span> root<span class="token punctuation">.</span>val <span class="token operator">+</span> <span class="token string">","</span><span class="token punctuation">;</span>      res <span class="token operator">+=</span> <span class="token function">serialize</span><span class="token punctuation">(</span>root<span class="token punctuation">.</span>left<span class="token punctuation">)</span><span class="token punctuation">;</span>      res <span class="token operator">+=</span> <span class="token function">serialize</span><span class="token punctuation">(</span>root<span class="token punctuation">.</span>right<span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token keyword">return</span> res<span class="token punctuation">;</span>  <span class="token punctuation">}</span>  <span class="token comment" spellcheck="true">// Decodes your encoded data to tree.</span>  <span class="token keyword">public</span> TreeNode <span class="token function">deserialize</span><span class="token punctuation">(</span>String data<span class="token punctuation">)</span> <span class="token punctuation">{</span>      String<span class="token punctuation">[</span><span class="token punctuation">]</span> arr <span class="token operator">=</span> data<span class="token punctuation">.</span><span class="token function">split</span><span class="token punctuation">(</span><span class="token string">","</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      Queue<span class="token operator">&lt;</span>String<span class="token operator">></span> queue <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">LinkedList</span><span class="token operator">&lt;</span>String<span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> arr<span class="token punctuation">.</span>length<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span><span class="token punctuation">{</span>          queue<span class="token punctuation">.</span><span class="token function">offer</span><span class="token punctuation">(</span>arr<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token punctuation">}</span>      <span class="token keyword">return</span> <span class="token function">dfs</span><span class="token punctuation">(</span>queue<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">}</span>  <span class="token keyword">public</span> TreeNode <span class="token function">dfs</span><span class="token punctuation">(</span>Queue<span class="token operator">&lt;</span>String<span class="token operator">></span> queue<span class="token punctuation">)</span><span class="token punctuation">{</span>      String val <span class="token operator">=</span> queue<span class="token punctuation">.</span><span class="token function">poll</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token keyword">if</span><span class="token punctuation">(</span>val<span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span><span class="token string">"null"</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span> null<span class="token punctuation">;</span>      TreeNode root <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">TreeNode</span><span class="token punctuation">(</span>Integer<span class="token punctuation">.</span><span class="token function">valueOf</span><span class="token punctuation">(</span>val<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      root<span class="token punctuation">.</span>left <span class="token operator">=</span> <span class="token function">dfs</span><span class="token punctuation">(</span>queue<span class="token punctuation">)</span><span class="token punctuation">;</span>      root<span class="token punctuation">.</span>right <span class="token operator">=</span> <span class="token function">dfs</span><span class="token punctuation">(</span>queue<span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token keyword">return</span> root<span class="token punctuation">;</span>  <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">//优化</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Codec</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// Encodes a tree to a single string.</span>    <span class="token keyword">public</span> String <span class="token function">serialize</span><span class="token punctuation">(</span>TreeNode root<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>root <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token string">"null"</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> root<span class="token punctuation">.</span>val <span class="token operator">+</span> <span class="token string">","</span> <span class="token operator">+</span> <span class="token function">serialize</span><span class="token punctuation">(</span>root<span class="token punctuation">.</span>left<span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">","</span> <span class="token operator">+</span> <span class="token function">serialize</span><span class="token punctuation">(</span>root<span class="token punctuation">.</span>right<span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">// Decodes your encoded data to tree.</span>    <span class="token keyword">public</span> TreeNode <span class="token function">deserialize</span><span class="token punctuation">(</span>String data<span class="token punctuation">)</span> <span class="token punctuation">{</span>        Queue<span class="token operator">&lt;</span>String<span class="token operator">></span> queue <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">LinkedList</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span>Arrays<span class="token punctuation">.</span><span class="token function">asList</span><span class="token punctuation">(</span>data<span class="token punctuation">.</span><span class="token function">split</span><span class="token punctuation">(</span><span class="token string">","</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> <span class="token function">dfs</span><span class="token punctuation">(</span>queue<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">private</span> TreeNode <span class="token function">dfs</span><span class="token punctuation">(</span>Queue<span class="token operator">&lt;</span>String<span class="token operator">></span> queue<span class="token punctuation">)</span> <span class="token punctuation">{</span>        String val <span class="token operator">=</span> queue<span class="token punctuation">.</span><span class="token function">poll</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">if</span><span class="token punctuation">(</span><span class="token string">"null"</span><span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span>val<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span> null<span class="token punctuation">;</span>        TreeNode root <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">TreeNode</span><span class="token punctuation">(</span>Integer<span class="token punctuation">.</span><span class="token function">parseInt</span><span class="token punctuation">(</span>val<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        root<span class="token punctuation">.</span>left <span class="token operator">=</span> <span class="token function">dfs</span><span class="token punctuation">(</span>queue<span class="token punctuation">)</span><span class="token punctuation">;</span>        root<span class="token punctuation">.</span>right <span class="token operator">=</span> <span class="token function">dfs</span><span class="token punctuation">(</span>queue<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> root<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h4 id="方法2：BFS"><a href="#方法2：BFS" class="headerlink" title="方法2：BFS"></a>方法2：BFS</h4><ul><li><strong>序列化</strong></li></ul><ol><li>用BFS遍历树，与一般遍历的不同点是不管node的左右子节点是否存在，统统加到队列中</li><li>在节点出队时，如果节点不存在，在返回值res中加入一个”null”；如果节点存在，则加入节点值的字符串形式</li></ol><ul><li><strong>反序列化</strong></li></ul><ol><li>同样使用BFS方法，利用队列新建二叉树</li><li>首先要将data转换成列表，然后遍历，只要不为null将节点按顺序加入二叉树中；同时还要将节点入队</li><li>队列为空时遍历完毕，返回根节点</li></ol><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Codec</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// Encodes a tree to a single string.</span>    <span class="token keyword">public</span> String <span class="token function">serialize</span><span class="token punctuation">(</span>TreeNode root<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>root <span class="token operator">==</span> null<span class="token punctuation">)</span><span class="token punctuation">{</span>            <span class="token keyword">return</span> <span class="token string">"[]"</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        StringBuilder res <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">StringBuilder</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        res<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span><span class="token string">"["</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        Queue<span class="token operator">&lt;</span>TreeNode<span class="token operator">></span> queue <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">LinkedList</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        queue<span class="token punctuation">.</span><span class="token function">offer</span><span class="token punctuation">(</span>root<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">while</span><span class="token punctuation">(</span><span class="token operator">!</span>queue<span class="token punctuation">.</span><span class="token function">isEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">{</span>            TreeNode node <span class="token operator">=</span> queue<span class="token punctuation">.</span><span class="token function">poll</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span>node <span class="token operator">!=</span> null<span class="token punctuation">)</span><span class="token punctuation">{</span>                res<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span>node<span class="token punctuation">.</span>val<span class="token punctuation">)</span><span class="token punctuation">;</span>                queue<span class="token punctuation">.</span><span class="token function">offer</span><span class="token punctuation">(</span>node<span class="token punctuation">.</span>left<span class="token punctuation">)</span><span class="token punctuation">;</span>                queue<span class="token punctuation">.</span><span class="token function">offer</span><span class="token punctuation">(</span>node<span class="token punctuation">.</span>right<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span><span class="token keyword">else</span> <span class="token punctuation">{</span>                res<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span><span class="token string">"null"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>            res<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span><span class="token string">","</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        res<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span><span class="token string">"]"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> res<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">// Decodes your encoded data to tree.</span>    <span class="token keyword">public</span> TreeNode <span class="token function">deserialize</span><span class="token punctuation">(</span>String data<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>data <span class="token operator">==</span> <span class="token string">"[]"</span><span class="token punctuation">)</span><span class="token punctuation">{</span>            <span class="token keyword">return</span> null<span class="token punctuation">;</span>        <span class="token punctuation">}</span>        String<span class="token punctuation">[</span><span class="token punctuation">]</span> dataList <span class="token operator">=</span> data<span class="token punctuation">.</span><span class="token function">substring</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> data<span class="token punctuation">.</span><span class="token function">length</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">split</span><span class="token punctuation">(</span><span class="token string">","</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        TreeNode root <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">TreeNode</span><span class="token punctuation">(</span>Integer<span class="token punctuation">.</span><span class="token function">parseInt</span><span class="token punctuation">(</span>dataList<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        Queue<span class="token operator">&lt;</span>TreeNode<span class="token operator">></span> queue <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">LinkedList</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        queue<span class="token punctuation">.</span><span class="token function">offer</span><span class="token punctuation">(</span>root<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>        <span class="token keyword">while</span><span class="token punctuation">(</span><span class="token operator">!</span>queue<span class="token punctuation">.</span><span class="token function">isEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">{</span>            TreeNode node <span class="token operator">=</span> queue<span class="token punctuation">.</span><span class="token function">poll</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">if</span><span class="token punctuation">(</span><span class="token operator">!</span>dataList<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span><span class="token string">"null"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">{</span>                node<span class="token punctuation">.</span>left <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">TreeNode</span><span class="token punctuation">(</span>Integer<span class="token punctuation">.</span><span class="token function">parseInt</span><span class="token punctuation">(</span>dataList<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                queue<span class="token punctuation">.</span><span class="token function">offer</span><span class="token punctuation">(</span>node<span class="token punctuation">.</span>left<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>            i<span class="token operator">++</span><span class="token punctuation">;</span>            <span class="token keyword">if</span><span class="token punctuation">(</span><span class="token operator">!</span>dataList<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span><span class="token string">"null"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">{</span>                node<span class="token punctuation">.</span>right <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">TreeNode</span><span class="token punctuation">(</span>Integer<span class="token punctuation">.</span><span class="token function">parseInt</span><span class="token punctuation">(</span>dataList<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                queue<span class="token punctuation">.</span><span class="token function">offer</span><span class="token punctuation">(</span>node<span class="token punctuation">.</span>right<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>            i<span class="token operator">++</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> root<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="复杂度-55"><a href="#复杂度-55" class="headerlink" title="复杂度"></a>复杂度</h3><p>方法1</p><ul><li>时间复杂度：O(n)</li><li>空间复杂度：O(n)</li></ul><p>方法2</p><ul><li>时间复杂度：O(n)</li><li>空间复杂度：O(n)</li></ul><h2 id="38-字符串的排列"><a href="#38-字符串的排列" class="headerlink" title="38. 字符串的排列"></a>38. 字符串的排列</h2><h3 id="题目-66"><a href="#题目-66" class="headerlink" title="题目"></a>题目</h3><blockquote><p>输入一个字符串，打印出该字符串中字符的所有排列。</p><p>你可以以任意顺序返回这个字符串数组，但里面不能有重复元素。</p><p><strong>示例:</strong></p><pre><code>输入：s = &quot;abc&quot;输出：[&quot;abc&quot;,&quot;acb&quot;,&quot;bac&quot;,&quot;bca&quot;,&quot;cab&quot;,&quot;cba&quot;]</code></pre></blockquote><h3 id="解答-66"><a href="#解答-66" class="headerlink" title="解答"></a>解答</h3><pre class="line-numbers language-java"><code class="language-java"><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><h3 id="复杂度-56"><a href="#复杂度-56" class="headerlink" title="复杂度"></a>复杂度</h3><h1 id="第-29-天-动态规划（困难）"><a href="#第-29-天-动态规划（困难）" class="headerlink" title="第 29 天 动态规划（困难）"></a>第 29 天 动态规划（困难）</h1><h2 id="19-正则表达式匹配"><a href="#19-正则表达式匹配" class="headerlink" title="19. 正则表达式匹配"></a>19. 正则表达式匹配</h2><h3 id="题目-67"><a href="#题目-67" class="headerlink" title="题目"></a>题目</h3><blockquote><p>请实现一个函数用来匹配包含<code>&#39;. &#39;</code>和<code>&#39;*&#39;</code>的正则表达式。模式中的字符<code>&#39;.&#39;</code>表示任意一个字符，而<code>&#39;*&#39;</code>表示它前面的字符可以出现任意次（含0次）。在本题中，匹配是指字符串的所有字符匹配整个模式。例如，字符串<code>&quot;aaa&quot;</code>与模式<code>&quot;a.a&quot;</code>和<code>&quot;ab*ac*a&quot;</code>匹配，但与<code>&quot;aa.a&quot;</code>和<code>&quot;ab*a&quot;</code>均不匹配。</p><p><strong>示例 1:</strong></p><pre><code>输入:s = &quot;aa&quot;p = &quot;a&quot;输出: false解释: &quot;a&quot; 无法匹配 &quot;aa&quot; 整个字符串。</code></pre><p><strong>示例 2:</strong></p><pre><code>输入:s = &quot;aa&quot;p = &quot;a*&quot;输出: true解释: 因为 &#39;*&#39; 代表可以匹配零个或多个前面的那一个元素, 在这里前面的元素就是 &#39;a&#39;。因此，字符串 &quot;aa&quot; 可被视为 &#39;a&#39; 重复了一次。</code></pre><p><strong>示例 3:</strong></p><pre><code>输入:s = &quot;ab&quot;p = &quot;.*&quot;输出: true解释: &quot;.*&quot; 表示可匹配零个或多个（&#39;*&#39;）任意字符（&#39;.&#39;）。</code></pre><p><strong>示例 4:</strong></p><pre><code>输入:s = &quot;aab&quot;p = &quot;c*a*b&quot;输出: true解释: 因为 &#39;*&#39; 表示零个或多个，这里 &#39;c&#39; 为 0 个, &#39;a&#39; 被重复一次。因此可以匹配字符串 &quot;aab&quot;。</code></pre><p><strong>示例 5:</strong></p><pre><code>输入:s = &quot;mississippi&quot;p = &quot;mis*is*p*.&quot;输出: false</code></pre><ul><li><code>s</code> 可能为空，且只包含从 <code>a-z</code> 的小写字母。</li><li><code>p</code> 可能为空，且只包含从 <code>a-z</code> 的小写字母以及字符 <code>.</code> 和 <code>*</code>，无连续的 <code>&#39;*&#39;</code>。</li></ul></blockquote><h3 id="解答-67"><a href="#解答-67" class="headerlink" title="解答"></a>解答</h3><pre class="line-numbers language-java"><code class="language-java"><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><h3 id="复杂度-57"><a href="#复杂度-57" class="headerlink" title="复杂度"></a>复杂度</h3><h2 id="49-丑数"><a href="#49-丑数" class="headerlink" title="49. 丑数"></a>49. 丑数</h2><h3 id="题目-68"><a href="#题目-68" class="headerlink" title="题目"></a>题目</h3><blockquote><p>我们把只包含质因子 2、3 和 5 的数称作丑数（Ugly Number）。求按从小到大的顺序的第 n 个丑数。</p><p>示例:</p><pre><code>输入: n = 10输出: 12解释: 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 是前 10 个丑数。</code></pre></blockquote><h3 id="解答-68"><a href="#解答-68" class="headerlink" title="解答"></a>解答</h3><p>丑数的递推性质： 丑数只包含因子 2, 3, 5 ，因此有 “丑数 &#x3D;&#x3D; 某较小丑数× 2&#x2F;3&#x2F;5” 。</p><p>设已知长度为 n 的丑数序列 x<sub>1</sub>, x<sub>2</sub>, ⋯ , x<sub>n</sub> ，求第 n+1 个丑数 x<sub>n+1</sub>。根根据递推性质，丑数  x<sub>n+1</sub> 只可能是以下三种情况其中之一（索引 a,b,c 为未知数）：</p><p><img src="/../images/LeetCode-%E5%89%91%E6%8C%87offer/image-20220913140851459.png"></p><p><strong>丑数递推公式：</strong> 若索引 a,b,c 满足以上条件，则下个丑数  x<sub>n+1</sub> &#x3D; min⁡(x<sub>a</sub>×2, x<sub>b</sub>×3, x<sub>c</sub>×5)</p><p>因此，可设置指针 a,b,c 指向首个丑数（即 1 ），循环根据递推公式得到下个丑数，并每轮将对应指针执行 +1 即可。</p><h5 id="动态规划解析：-1"><a href="#动态规划解析：-1" class="headerlink" title="动态规划解析："></a>动态规划解析：</h5><ul><li><p><strong>状态定义：</strong> 设动态规划列表 dp ，dp[i] 代表第 i+1 个丑数；</p></li><li><p><strong>转移方程：</strong></p><ol><li>当索引 a,b,c 满足以下条件时， dp[i] 为三种情况的最小值；</li><li>每轮计算 dp[i] &#x3D; min⁡(dp[a]×2, dp[b]×3, dp[c]×5) 后，需要更新索引 a,b,c 的值。实现方法：<strong>分别独立判断</strong> dp[i] 和 dp[a]×2 , dp[b]×3 , dp[c]×5 的大小关系，若相等则将对应索引 a , b , c 加 1 ；</li></ol></li><li><p><strong>初始状态：</strong> dp[0]=1 ，即第一个丑数为 1 ；</p></li><li><p><strong>返回值：</strong> dp[n−1]，即返回第 n 个丑数；</p></li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>  <span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">nthUglyNumber</span><span class="token punctuation">(</span><span class="token keyword">int</span> n<span class="token punctuation">)</span><span class="token punctuation">{</span>    <span class="token keyword">int</span> a <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">,</span> b <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">,</span> c <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>    <span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> dp <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">int</span><span class="token punctuation">[</span>n<span class="token punctuation">]</span><span class="token punctuation">;</span>    dp<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>    <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> n<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span><span class="token punctuation">{</span>      <span class="token keyword">int</span> x <span class="token operator">=</span> dp<span class="token punctuation">[</span>a<span class="token punctuation">]</span> <span class="token operator">*</span> <span class="token number">2</span><span class="token punctuation">,</span> y <span class="token operator">=</span> dp<span class="token punctuation">[</span>b<span class="token punctuation">]</span> <span class="token operator">*</span> <span class="token number">3</span><span class="token punctuation">,</span> z <span class="token operator">=</span> dp<span class="token punctuation">[</span>c<span class="token punctuation">]</span> <span class="token operator">*</span> <span class="token number">5</span><span class="token punctuation">;</span>      dp<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> Math<span class="token punctuation">.</span><span class="token function">min</span><span class="token punctuation">(</span>Math<span class="token punctuation">.</span><span class="token function">min</span><span class="token punctuation">(</span>x<span class="token punctuation">,</span>y<span class="token punctuation">)</span><span class="token punctuation">,</span>z<span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token keyword">if</span><span class="token punctuation">(</span>dp<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">==</span> x<span class="token punctuation">)</span> a<span class="token operator">++</span><span class="token punctuation">;</span>      <span class="token keyword">if</span><span class="token punctuation">(</span>dp<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">==</span> y<span class="token punctuation">)</span> b<span class="token operator">++</span><span class="token punctuation">;</span>      <span class="token keyword">if</span><span class="token punctuation">(</span>dp<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">==</span> z<span class="token punctuation">)</span> c<span class="token operator">++</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">return</span> dp<span class="token punctuation">[</span>n<span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">;</span>  <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="复杂度-58"><a href="#复杂度-58" class="headerlink" title="复杂度"></a>复杂度</h3><ul><li><strong>时间复杂度 O(N) ：</strong> 其中 N=n ，动态规划需遍历计算 dp 列表。</li><li><strong>空间复杂度 O(N) ：</strong> 长度为 N 的 dp 列表使用 O(N) 的额外空间。</li></ul><h2 id="60-n个骰子的点数"><a href="#60-n个骰子的点数" class="headerlink" title="60. n个骰子的点数"></a>60. n个骰子的点数</h2><h3 id="题目-69"><a href="#题目-69" class="headerlink" title="题目"></a>题目</h3><blockquote><p>把n个骰子扔在地上，所有骰子朝上一面的点数之和为s。输入n，打印出s的所有可能的值出现的概率。</p><p>你需要用一个浮点数数组返回答案，其中第 i 个元素代表这 n 个骰子所能掷出的点数集合中第 i 小的那个的概率。</p><p>示例 1:</p><pre><code>输入: 1输出: [0.16667,0.16667,0.16667,0.16667,0.16667,0.16667]</code></pre><p>示例 2:</p><pre><code>输入: 2输出: [0.02778,0.05556,0.08333,0.11111,0.13889,0.16667,0.13889,0.11111,0.08333,0.05556,0.02778]</code></pre></blockquote><h3 id="解答-69"><a href="#解答-69" class="headerlink" title="解答"></a>解答</h3><p>现在有 <code>n</code> 个骰子，点数和的范围为 [n, 6n] ，长度为 6n - n + 1 &#x3D; 5n + 1，扔出点数 <code>x</code> 的概率是多少？</p><p>首先，<code>1</code> 个骰子能扔出的点数是 <code>1~6</code>，那么 <code>n</code> 个骰子扔出点数 <code>x</code> 的概率就可以通过 <code>n-1</code> 个骰子扔出点数 <code>x-1, x-2,... x-6</code> 的概率分别乘以 1&#x2F;6 再相加得到。</p><p>如果定义一个 <code>f(n, x)</code> 函数表示用 n 个骰子抛出 x 点数的概率，那么这个状态转移关系如下：</p><p><img src="https://img.jwt1399.top/img/202209151252298.png"></p><p><code>f(n−1,x−i)</code>中的 <code>x−i</code> 会有越界问题。例如，若希望递推计算 <code>f(2,2)</code>，由于一个骰子的点数和范围为 [1,6] ，因此只应求和 f(1,1) ，即 <code>f(1,0), f(1,−1), ..., f(1,−4)</code> 皆无意义。因此需要 <code>x−i &gt; 0</code> </p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// 自底向上的迭代解法</span><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">double</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token function">dicesProbability</span><span class="token punctuation">(</span><span class="token keyword">int</span> n<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// n 个骰子可能扔出的结果的最大值和最小值</span>        <span class="token keyword">int</span> min <span class="token operator">=</span> n<span class="token punctuation">,</span> max <span class="token operator">=</span> n <span class="token operator">*</span> <span class="token number">6</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 定义：用 n 个骰子，凑出 x 的点数的概率是 dp[n][x]</span>        <span class="token keyword">double</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token punctuation">]</span> dp <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">double</span><span class="token punctuation">[</span>n <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">[</span>max <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 一个骰子扔出点数 1~6 的概率是 1/6</span>        <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> j <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> j <span class="token operator">&lt;=</span> <span class="token number">6</span><span class="token punctuation">;</span> j<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            dp<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">[</span>j<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token number">1</span> <span class="token operator">/</span> <span class="token number">6.0</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 状态转移</span>        <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">2</span><span class="token punctuation">;</span> i <span class="token operator">&lt;=</span> n<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> j <span class="token operator">=</span> i <span class="token operator">*</span> <span class="token number">1</span><span class="token punctuation">;</span> j <span class="token operator">&lt;=</span> i <span class="token operator">*</span> <span class="token number">6</span><span class="token punctuation">;</span> j<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> k <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> k <span class="token operator">&lt;=</span> <span class="token number">6</span><span class="token punctuation">;</span> k<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                    <span class="token keyword">if</span> <span class="token punctuation">(</span>j <span class="token operator">-</span> k <span class="token operator">&lt;=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                        <span class="token keyword">break</span><span class="token punctuation">;</span>                    <span class="token punctuation">}</span>                    <span class="token comment" spellcheck="true">// i 个骰子扔出点数 j 的概率</span>                    <span class="token comment" spellcheck="true">// 可以通过 i - 1 个骰子扔出点数 j - k 的概率推倒出来</span>                    dp<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">[</span>j<span class="token punctuation">]</span> <span class="token operator">+=</span> dp<span class="token punctuation">[</span>i <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">[</span>j <span class="token operator">-</span> k<span class="token punctuation">]</span> <span class="token operator">*</span> <span class="token number">1</span> <span class="token operator">/</span> <span class="token number">6.0</span><span class="token punctuation">;</span>                <span class="token punctuation">}</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">//点数和的范围为 [n, 6n] ，长度为 6n - n + 1 = 5n + 1</span>        <span class="token keyword">double</span><span class="token punctuation">[</span><span class="token punctuation">]</span> res <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">double</span><span class="token punctuation">[</span>max <span class="token operator">-</span> min <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">;</span>        <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> res<span class="token punctuation">.</span>length<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            res<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> dp<span class="token punctuation">[</span>n<span class="token punctuation">]</span><span class="token punctuation">[</span>min <span class="token operator">+</span> i<span class="token punctuation">]</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> res<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="复杂度-59"><a href="#复杂度-59" class="headerlink" title="复杂度"></a>复杂度</h3><ul><li><strong>时间复杂度 O(n<sup>2</sup>) ：</strong> 状态转移循环 n−1 轮；每轮中，当 i = 2,3,…,n 时，对应循环数量分别为 6×6,11×6,…,(5n+1)×6 ；因此总体复杂度为 O((n−1)×{[6+(5n+1)]&#x2F;2}×6)，即等价于O(n<sup>2</sup>) 。</li><li><strong>空间复杂度 O(n<sup>2</sup>)：</strong>n*(5n+1)</li></ul><h1 id="第-30-天-分治算法（困难）"><a href="#第-30-天-分治算法（困难）" class="headerlink" title="第 30 天 分治算法（困难）"></a>第 30 天 分治算法（困难）</h1><h2 id="17-打印从1到最大的n位数"><a href="#17-打印从1到最大的n位数" class="headerlink" title="17. 打印从1到最大的n位数"></a>17. 打印从1到最大的n位数</h2><h3 id="题目-70"><a href="#题目-70" class="headerlink" title="题目"></a>题目</h3><blockquote><p>输入数字 <code>n</code>，按顺序打印出从 1 到最大的 n 位十进制数。比如输入 3，则打印出 1、2、3 一直到最大的 3 位数 999。</p><p><strong>示例 1:</strong></p><pre><code>输入: n = 1输出: [1,2,3,4,5,6,7,8,9]</code></pre></blockquote><h3 id="解答-70"><a href="#解答-70" class="headerlink" title="解答"></a>解答</h3><p>如果不考虑大数问题，找出右边界，然后循环即可！！</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">Solution</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token function">printNumbers</span><span class="token punctuation">(</span><span class="token keyword">int</span> n<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">int</span> end <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">)</span>Math<span class="token punctuation">.</span><span class="token function">pow</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">,</span>n<span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">;</span>         <span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> res <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">int</span><span class="token punctuation">[</span>end<span class="token punctuation">]</span><span class="token punctuation">;</span>        <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> end<span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span><span class="token punctuation">{</span>            res<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> i <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> res<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>考虑大数越界情况</p><p>。。。。</p><h3 id="复杂度-60"><a href="#复杂度-60" class="headerlink" title="复杂度"></a>复杂度</h3><ul><li><strong>时间复杂度 O(10<sup>n</sup>) ：</strong> 生成长度为 10<sup>n</sup> 的列表需使用 O(10<sup>n</sup>) 时间。</li><li><strong>空间复杂度 O(1) ：</strong> 建立列表需使用 O(1) 大小的额外空间（ 列表作为返回结果，不计入额外空间 ）。</li></ul><h2 id="51-数组中的逆序对"><a href="#51-数组中的逆序对" class="headerlink" title="51. 数组中的逆序对"></a>51. 数组中的逆序对</h2><h3 id="题目-71"><a href="#题目-71" class="headerlink" title="题目"></a>题目</h3><blockquote></blockquote><h3 id="解答-71"><a href="#解答-71" class="headerlink" title="解答"></a>解答</h3><pre class="line-numbers language-java"><code class="language-java"><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><h3 id="复杂度-61"><a href="#复杂度-61" class="headerlink" title="复杂度"></a>复杂度</h3><h1 id="第-31-天-数学（困难）"><a href="#第-31-天-数学（困难）" class="headerlink" title="第 31 天 数学（困难）"></a>第 31 天 数学（困难）</h1><h2 id="14-II-剪绳子-II"><a href="#14-II-剪绳子-II" class="headerlink" title="14- II. 剪绳子 II"></a>14- II. 剪绳子 II</h2><h3 id="题目-72"><a href="#题目-72" class="headerlink" title="题目"></a>题目</h3><blockquote></blockquote><h3 id="解答-72"><a href="#解答-72" class="headerlink" title="解答"></a>解答</h3><pre class="line-numbers language-java"><code class="language-java"><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><h3 id="复杂度-62"><a href="#复杂度-62" class="headerlink" title="复杂度"></a>复杂度</h3><h2 id="43-1～n-整数中-1-出现的次数"><a href="#43-1～n-整数中-1-出现的次数" class="headerlink" title="43. 1～n 整数中 1 出现的次数"></a>43. 1～n 整数中 1 出现的次数</h2><h3 id="题目-73"><a href="#题目-73" class="headerlink" title="题目"></a>题目</h3><blockquote></blockquote><h3 id="解答-73"><a href="#解答-73" class="headerlink" title="解答"></a>解答</h3><pre class="line-numbers language-java"><code class="language-java"><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><h3 id="复杂度-63"><a href="#复杂度-63" class="headerlink" title="复杂度"></a>复杂度</h3><h2 id="44-数字序列中某一位的数字"><a href="#44-数字序列中某一位的数字" class="headerlink" title="44. 数字序列中某一位的数字"></a>44. 数字序列中某一位的数字</h2><h3 id="题目-74"><a href="#题目-74" class="headerlink" title="题目"></a>题目</h3><blockquote></blockquote><h3 id="解答-74"><a href="#解答-74" class="headerlink" title="解答"></a>解答</h3><pre class="line-numbers language-java"><code class="language-java"><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><h3 id="复杂度-64"><a href="#复杂度-64" class="headerlink" title="复杂度"></a>复杂度</h3><h1 id="❤️Sponsor"><a href="#❤️Sponsor" class="headerlink" title="❤️Sponsor"></a>❤️Sponsor</h1><p>您的支持是我不断前进的动力，如果你觉得本文对你有帮助，你可以请我喝一杯冰可乐！嘻嘻🤭</p><table>  <tbody>     <tr>         <td style="text-align:center;">支付宝支付</td>         <td style="text-align:center;">微信支付</td>     </tr>   <tr>    <td style="text-align:center;" ><img width="200" src="https://jwt1399.top/medias/reward/alipay.png"></td>          <td style="text-align:center;"><img width="200" src="https://jwt1399.top/medias/reward/sponor_wechat.png"></td>       </tr></tbody></table>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;p&gt;首刷剑指offer，刷起来还是比较吃力，大多数题需要看题解才能做出来，甚至有的看了题解都不懂😭，我是废物，希望第二次刷的时候大部分题都能自己做出来吧！！！&lt;/p&gt;
&lt;h1 id=&quot;剑指Offer&quot;&gt;&lt;a href=&quot;#剑指Offer&quot;</summary>
        
      
    
    
    
    <category term="结构-算法" scheme="https://jwt1399.top/categories/%E7%BB%93%E6%9E%84-%E7%AE%97%E6%B3%95/"/>
    
    
    <category term="算法" scheme="https://jwt1399.top/tags/%E7%AE%97%E6%B3%95/"/>
    
    <category term="数据结构" scheme="https://jwt1399.top/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/"/>
    
  </entry>
  
  <entry>
    <title>Redis-实战篇</title>
    <link href="https://jwt1399.top/posts/32967.html"/>
    <id>https://jwt1399.top/posts/32967.html</id>
    <published>2022-08-08T06:45:43.000Z</published>
    <updated>2023-03-01T09:25:00.437Z</updated>
    
    <content type="html"><![CDATA[<h1 id="⓪系统简介"><a href="#⓪系统简介" class="headerlink" title="⓪系统简介"></a>⓪系统简介</h1><ol><li><p><strong>短信登录</strong>：使用Redis共享session来实现</p></li><li><p><strong>商户查询缓存</strong>：理解缓存击穿，缓存穿透，缓存雪崩等问题</p></li><li><p><strong>优惠卷秒杀</strong></p><ul><li><p>学会Redis的计数器功能， 结合Lua完成高性能的redis操作</p></li><li><p>学会Redis分布式锁的原理，包括Redis的三种消息队列</p></li></ul></li><li><p><strong>附近的商户</strong>：利用Redis的GEO来完成对于地理坐标的操作</p></li><li><p><strong>UV统计</strong>：使用Redis的HyperLogLog来完成统计功能</p></li><li><p><strong>用户签到</strong>：使用Redis的BitMap数据统计功能</p></li><li><p><strong>好友关注</strong>：基于Set集合的关注、取消关注，共同关注等等功能</p></li><li><p><strong>达人探店</strong>：基于List完成点赞列表的操作，基于SortedSet来完成点赞的排行榜功能</p></li></ol><p><img src="https://img.jwt1399.top/img/202210261714909.png"></p><h1 id="①短信登录"><a href="#①短信登录" class="headerlink" title="①短信登录"></a>①短信登录</h1><h2 id="❶基于session实现登录"><a href="#❶基于session实现登录" class="headerlink" title="❶基于session实现登录"></a>❶基于session实现登录</h2><h3 id="0-登录流程分析"><a href="#0-登录流程分析" class="headerlink" title="0.登录流程分析"></a>0.登录流程分析</h3><p><img src="https://img.jwt1399.top/img/202210121713673.png" alt="基于Session实现登录流程"></p><p><strong>1.发送短信验证码：</strong></p><p>用户在提交手机号后，会校验手机号是否合法，</p><ul><li><p>如果不合法，则要求用户重新输入手机号</p></li><li><p>如果合法，后台生成对应的验证码，同时将验证码进行保存，然后再通过短信将验证码发送给用户</p></li></ul><p><strong>2.验证码登录&#x2F;注册：</strong></p><p>用户将验证码和手机号进行输入，后台从<code>session</code>中拿到当前验证码，然后和用户输入的验证码进行校验，</p><ul><li>如果不一致，则无法通过校验；</li><li>如果一致，则后台根据手机号查询用户；<ul><li>如果用户不存在，则为用户创建账号信息，保存到数据库，并用户信息保存到<code>session</code>中，</li><li>如果用户存在，将用户信息保存到<code>session</code>中，</li><li>无论是否存在，都将用户信息保存到<code>session</code>中是为了方便后续获得当前登录信息</li></ul></li></ul><p><strong>3.校验登录状态:</strong></p><p>用户请求时，会从<code>cookie</code>中携带<code>JsessionId</code>到后台，后台通过<code>JsessionId</code>从<code>session</code>中拿到用户信息，</p><ul><li>如果没有<code>session</code>信息，则进行拦截，</li><li>如果有<code>session</code>信息，则将用户信息保存到<code>threadLocal</code>中，并且放行</li></ul><h3 id="1-发送验证码"><a href="#1-发送验证码" class="headerlink" title="1.发送验证码"></a>1.发送验证码</h3><p><img src="https://img.jwt1399.top/img/202210121713058.png" alt="页面流程"></p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">package</span> com<span class="token punctuation">.</span>kbdp<span class="token punctuation">.</span>service<span class="token punctuation">.</span>impl<span class="token punctuation">;</span><span class="token annotation punctuation">@Service</span><span class="token annotation punctuation">@Slf4j</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">UserServiceImpl</span> <span class="token keyword">extends</span> <span class="token class-name">ServiceImpl</span><span class="token operator">&lt;</span>UserMapper<span class="token punctuation">,</span> User<span class="token operator">></span> <span class="token keyword">implements</span> <span class="token class-name">IUserService</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> Result <span class="token function">sendCode</span><span class="token punctuation">(</span>String phone<span class="token punctuation">,</span> HttpSession session<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 1.校验手机号</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>RegexUtils<span class="token punctuation">.</span><span class="token function">isPhoneInvalid</span><span class="token punctuation">(</span>phone<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 2.如果不符合，返回错误信息</span>            <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">fail</span><span class="token punctuation">(</span><span class="token string">"手机号格式错误！"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 3.符合，生成验证码</span>        String code <span class="token operator">=</span> RandomUtil<span class="token punctuation">.</span><span class="token function">randomNumbers</span><span class="token punctuation">(</span><span class="token number">6</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 4.保存验证码到 session</span>        session<span class="token punctuation">.</span><span class="token function">setAttribute</span><span class="token punctuation">(</span><span class="token string">"code"</span><span class="token punctuation">,</span>code<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 5.发送验证码</span>        log<span class="token punctuation">.</span><span class="token function">debug</span><span class="token punctuation">(</span><span class="token string">"发送短信验证码成功，验证码：{}"</span><span class="token punctuation">,</span> code<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 返回ok</span>        <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">ok</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="2-登录-x2F-注册"><a href="#2-登录-x2F-注册" class="headerlink" title="2.登录&#x2F;注册"></a>2.登录&#x2F;注册</h3><p><img src="https://img.jwt1399.top/img/202210132211795.png" alt="页面流程"></p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">package</span> com<span class="token punctuation">.</span>kbdp<span class="token punctuation">.</span>service<span class="token punctuation">.</span>impl<span class="token punctuation">;</span><span class="token annotation punctuation">@Service</span><span class="token annotation punctuation">@Slf4j</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">UserServiceImpl</span> <span class="token keyword">extends</span> <span class="token class-name">ServiceImpl</span><span class="token operator">&lt;</span>UserMapper<span class="token punctuation">,</span> User<span class="token operator">></span> <span class="token keyword">implements</span> <span class="token class-name">IUserService</span> <span class="token punctuation">{</span>  <span class="token annotation punctuation">@Override</span>  <span class="token keyword">public</span> Result <span class="token function">login</span><span class="token punctuation">(</span>LoginFormDTO loginForm<span class="token punctuation">,</span> HttpSession session<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 1.校验手机号</span>    String phone <span class="token operator">=</span> loginForm<span class="token punctuation">.</span><span class="token function">getPhone</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>RegexUtils<span class="token punctuation">.</span><span class="token function">isPhoneInvalid</span><span class="token punctuation">(</span>phone<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>      <span class="token comment" spellcheck="true">// 2.如果不符合，返回错误信息</span>      <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">fail</span><span class="token punctuation">(</span><span class="token string">"手机号格式错误！"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">// 3.校验验证码</span>    Object cacheCode <span class="token operator">=</span> session<span class="token punctuation">.</span><span class="token function">getAttribute</span><span class="token punctuation">(</span><span class="token string">"code"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    String code <span class="token operator">=</span> loginForm<span class="token punctuation">.</span><span class="token function">getCode</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>cacheCode <span class="token operator">==</span> null <span class="token operator">||</span> <span class="token operator">!</span>cacheCode<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span>code<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>      <span class="token comment" spellcheck="true">//4.不一致，报错</span>      <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">fail</span><span class="token punctuation">(</span><span class="token string">"验证码错误"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">//5.一致，根据手机号查询用户</span>    <span class="token comment" spellcheck="true">//User user = query().eq("phone", phone).one();</span>    LambdaQueryWrapper<span class="token operator">&lt;</span>User<span class="token operator">></span> lqw <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">LambdaQueryWrapper</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    User user <span class="token operator">=</span> <span class="token function">getOne</span><span class="token punctuation">(</span>lqw<span class="token punctuation">.</span><span class="token function">eq</span><span class="token punctuation">(</span>User<span class="token operator">:</span><span class="token operator">:</span>getPhone<span class="token punctuation">,</span> phone<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">//6.判断用户是否存在</span>    <span class="token keyword">if</span><span class="token punctuation">(</span>user <span class="token operator">==</span> null<span class="token punctuation">)</span><span class="token punctuation">{</span>      <span class="token comment" spellcheck="true">//不存在，则创建</span>      user <span class="token operator">=</span>  <span class="token function">createUserWithPhone</span><span class="token punctuation">(</span>phone<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">//7.保存用户信息到session中</span>    session<span class="token punctuation">.</span><span class="token function">setAttribute</span><span class="token punctuation">(</span><span class="token string">"user"</span><span class="token punctuation">,</span>user<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">ok</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">}</span>  <span class="token keyword">private</span> User <span class="token function">createUserWithPhone</span><span class="token punctuation">(</span>String phone<span class="token punctuation">)</span> <span class="token punctuation">{</span>    User user <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">User</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    user<span class="token punctuation">.</span><span class="token function">setPhone</span><span class="token punctuation">(</span>phone<span class="token punctuation">)</span><span class="token punctuation">;</span>    user<span class="token punctuation">.</span><span class="token function">setNickName</span><span class="token punctuation">(</span>USER_NICK_NAME_PREFIX <span class="token operator">+</span> RandomUtil<span class="token punctuation">.</span><span class="token function">randomNumbers</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token function">save</span><span class="token punctuation">(</span>user<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> user<span class="token punctuation">;</span>  <span class="token punctuation">}</span>  <span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="3-登录验证"><a href="#3-登录验证" class="headerlink" title="3.登录验证"></a>3.登录验证</h3><h4 id="a-前置知识"><a href="#a-前置知识" class="headerlink" title="a.前置知识"></a>a.前置知识</h4><ul><li>Tomcat的运行原理</li></ul><p><img src="/../images/Redis-%E5%AE%9E%E6%88%98%E7%AF%87/1653068196656.png"></p><p>当用户发起请求时，会访问我们tomcat注册的端口，任何程序想要运行，都需要有一个线程对当前端口号进行监听，tomcat也不例外，当监听线程知道用户想要和tomcat连接时，那会由监听线程创建socket连接，socket都是成对出现的，用户通过socket互相传递数据，当tomcat端的socket接受到数据后，此时监听线程会从tomcat的线程池中取出一个线程执行用户请求，在我们的服务部署到tomcat后，线程会找到用户想要访问的工程，然后用这个线程转发到工程中的controller，service，dao中，并且访问对应的DB，在用户执行完请求后，再统一返回，再找到tomcat端的socket，再将数据写回到用户端的socket，完成请求和响应</p><p>通过以上讲解，我们可以得知每个用户其实对应都是去找tomcat线程池中的一个线程来完成工作的， 使用完成后再进行回收，既然每个请求都是独立的，所以在每个用户去访问我们的工程时，我们可以使用threadlocal来做到线程隔离，每个线程操作自己的一份数据</p><ul><li>Threadlocal</li></ul><p><code>ThreadLocal</code> 叫做本地线程变量，<code>ThreadLocal</code> 中填充的的是当前线程的变量，该变量对其他线程而言是封闭且隔离的</p><h4 id="b-具体实现"><a href="#b-具体实现" class="headerlink" title="b.具体实现"></a>b.具体实现</h4><p><img src="https://img.jwt1399.top/img/202210261714702.png"></p><ul><li>拦截器代码</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">LoginInterceptor</span> <span class="token keyword">implements</span> <span class="token class-name">HandlerInterceptor</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> <span class="token keyword">boolean</span> <span class="token function">preHandle</span><span class="token punctuation">(</span>HttpServletRequest request<span class="token punctuation">,</span> HttpServletResponse response<span class="token punctuation">,</span> Object handler<span class="token punctuation">)</span> <span class="token keyword">throws</span> Exception <span class="token punctuation">{</span>       <span class="token comment" spellcheck="true">//1.获取session</span>        HttpSession session <span class="token operator">=</span> request<span class="token punctuation">.</span><span class="token function">getSession</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">//2.获取session中的用户</span>        Object user <span class="token operator">=</span> session<span class="token punctuation">.</span><span class="token function">getAttribute</span><span class="token punctuation">(</span><span class="token string">"user"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">//3.判断用户是否存在</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>user <span class="token operator">==</span> null<span class="token punctuation">)</span><span class="token punctuation">{</span>              <span class="token comment" spellcheck="true">//4.不存在，拦截，返回401状态码</span>              response<span class="token punctuation">.</span><span class="token function">setStatus</span><span class="token punctuation">(</span><span class="token number">401</span><span class="token punctuation">)</span><span class="token punctuation">;</span>              <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">//5.存在，保存用户信息到Threadlocal</span>        UserHolder<span class="token punctuation">.</span><span class="token function">saveUser</span><span class="token punctuation">(</span><span class="token punctuation">(</span>User<span class="token punctuation">)</span>user<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">//6.放行</span>        <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>让拦截器生效</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Configuration</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">MvcConfig</span> <span class="token keyword">implements</span> <span class="token class-name">WebMvcConfigurer</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">addInterceptors</span><span class="token punctuation">(</span>InterceptorRegistry registry<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 登录拦截器</span>        registry<span class="token punctuation">.</span><span class="token function">addInterceptor</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">LoginInterceptor</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>                <span class="token punctuation">.</span><span class="token function">excludePathPatterns</span><span class="token punctuation">(</span>                        <span class="token string">"/shop/**"</span><span class="token punctuation">,</span>                        <span class="token string">"/voucher/**"</span><span class="token punctuation">,</span>                        <span class="token string">"/shop-type/**"</span><span class="token punctuation">,</span>                        <span class="token string">"/upload/**"</span><span class="token punctuation">,</span>                        <span class="token string">"/blog/hot"</span><span class="token punctuation">,</span>                        <span class="token string">"/user/code"</span><span class="token punctuation">,</span>                        <span class="token string">"/user/login"</span>                <span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="4-隐藏用户敏感信息"><a href="#4-隐藏用户敏感信息" class="headerlink" title="4.隐藏用户敏感信息"></a>4.隐藏用户敏感信息</h3><p>我们通过浏览器观察到此时用户的全部信息都在，这样极为不靠谱，所以我们应当在返回用户信息之前，将用户的敏感信息进行隐藏，采用的核心思路就是书写一个UserDTO对象，这个UserDTO对象就没有敏感信息了，我们在返回前，将有用户敏感信息的User对象转化成没有敏感信息的UserDTO对象，那么就能够避免这个尴尬的问题了</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Data</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">UserDTO</span> <span class="token punctuation">{</span>    <span class="token keyword">private</span> Long id<span class="token punctuation">;</span>    <span class="token keyword">private</span> String nickName<span class="token punctuation">;</span>    <span class="token keyword">private</span> String icon<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>在登录方法处修改</strong></p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">//7.保存用户信息到session中</span>session<span class="token punctuation">.</span><span class="token function">setAttribute</span><span class="token punctuation">(</span><span class="token string">"user"</span><span class="token punctuation">,</span> BeanUtils<span class="token punctuation">.</span><span class="token function">copyProperties</span><span class="token punctuation">(</span>user<span class="token punctuation">,</span>UserDTO<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p><strong>在拦截器处：</strong></p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">//5.存在，保存用户信息到Threadlocal</span>UserHolder<span class="token punctuation">.</span><span class="token function">saveUser</span><span class="token punctuation">(</span><span class="token punctuation">(</span>UserDTO<span class="token punctuation">)</span>user<span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p><strong>在UserHolder处：将user对象换成UserDTO</strong></p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">UserHolder</span> <span class="token punctuation">{</span>    <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> ThreadLocal<span class="token operator">&lt;</span>UserDTO<span class="token operator">></span> tl <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ThreadLocal</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">saveUser</span><span class="token punctuation">(</span>UserDTO user<span class="token punctuation">)</span><span class="token punctuation">{</span>        tl<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span>user<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> UserDTO <span class="token function">getUser</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>        <span class="token keyword">return</span> tl<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">removeUser</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>        tl<span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="❷集群的session共享问题"><a href="#❷集群的session共享问题" class="headerlink" title="❷集群的session共享问题"></a>❷集群的session共享问题</h2><blockquote><p><strong>集群的session 共享问题</strong>：多台Tomcat并不共享session存储空间，当请求切换到不同tomcat服务时导致数据丢失的问题。</p></blockquote><p><img src="https://img.jwt1399.top/img/202210261713817.png"></p><p>每个 tomcat 中都有一份属于自己的session，假设用户第一次访问第一台tomcat，并且把自己的信息存放到第一台服务器的session中，但是第二次这个用户访问到了第二台tomcat，那么在第二台服务器上，肯定没有第一台服务器存放的session，所以此时整个登录拦截功能就会出现问题，我们能如何解决这个问题呢？</p><p>早期的方案是session拷贝，就是说虽然每个tomcat上都有不同的session，但是每当任意一台服务器的session修改时，都会同步给其他的Tomcat服务器的session，这样的话，就可以实现session的共享了</p><p>但是这种方案具有两个大问题</p><ul><li><p>1、每台服务器中都有完整的一份session数据，服务器压力过大。</p></li><li><p>2、session拷贝数据时，可能会出现延迟</p></li></ul><p>所以后来采用的方案都是基于redis来完成，我们把session换成redis，redis数据本身就是共享的，就可以避免session共享的问题了</p><h2 id="❸Redis代替session实现登录"><a href="#❸Redis代替session实现登录" class="headerlink" title="❸Redis代替session实现登录"></a>❸Redis代替session实现登录</h2><h3 id="1-key设计"><a href="#1-key设计" class="headerlink" title="1.key设计"></a>1.key设计</h3><blockquote><p>利用redis来存储数据，那么到底使用哪种结构呢？</p></blockquote><p>由于存入的数据比较简单，我们可以考虑使用String，或者是使用Hash</p><ul><li><p>使用String结构，以JSON字符串来保存，比较直观，多占用一点空间(还需存<code>&#123;&#125;、&quot;&quot;</code>等字符)</p></li><li><p>使用Hash结构，可以将对象中的每个字段独立存储，可以针对单个字段做CRUD，并且内存占用更少</p></li></ul><table><thead><tr><th align="center">String</th><th align="center">Hash</th></tr></thead><tbody><tr><td align="center"><img src="https://img.jwt1399.top/img/202210261621745.png"></td><td align="center"><img src="https://img.jwt1399.top/img/202210261621354.png"></td></tr></tbody></table><p>在设计这个key的时候，我们之前讲过需要满足两点</p><p>1、key要具有唯一性</p><p>2、key要方便携带</p><p>如果我们采用phone：手机号这个的数据来存储当然是可以的，但是如果把这样的敏感数据存储到redis中并且从页面中带过来毕竟不太合适，所以我们在后台生成一个随机串token，然后让前端携带这个token就能完成我们的整体逻辑了</p><h3 id="2-新版登录流程"><a href="#2-新版登录流程" class="headerlink" title="2.新版登录流程"></a>2.新版登录流程</h3><p><img src="https://img.jwt1399.top/img/202210261713092.png"></p><p>当注册完成后，用户去登录会去校验用户提交的手机号和验证码，是否一致，如果一致，则根据手机号查询用户信息，不存在则新建，最后将用户数据保存到redis，并且生成token作为redis的key，当我们校验用户是否登录时，会去携带着token进行访问，从redis中取出token对应的value，判断是否存在这个数据，如果没有则拦截，如果存在则将其保存到threadLocal中，并且放行。</p><h3 id="3-具体实现"><a href="#3-具体实现" class="headerlink" title="3.具体实现"></a>3.具体实现</h3><p><strong>登录&#x2F;注册业务代码</strong></p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Service</span><span class="token annotation punctuation">@Slf4j</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">UserServiceImpl</span> <span class="token keyword">extends</span> <span class="token class-name">ServiceImpl</span><span class="token operator">&lt;</span>UserMapper<span class="token punctuation">,</span> User<span class="token operator">></span> <span class="token keyword">implements</span> <span class="token class-name">IUserService</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Resource</span>    <span class="token keyword">private</span> StringRedisTemplate stringRedisTemplate<span class="token punctuation">;</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> Result <span class="token function">sendCode</span><span class="token punctuation">(</span>String phone<span class="token punctuation">,</span> HttpSession session<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 1.校验手机号</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>RegexUtils<span class="token punctuation">.</span><span class="token function">isPhoneInvalid</span><span class="token punctuation">(</span>phone<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 2.如果不符合，返回错误信息</span>            <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">fail</span><span class="token punctuation">(</span><span class="token string">"手机号格式错误！"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 3.符合，生成验证码</span>        String code <span class="token operator">=</span> RandomUtil<span class="token punctuation">.</span><span class="token function">randomNumbers</span><span class="token punctuation">(</span><span class="token number">6</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 4.保存验证码到redis，并设置过期时间</span>        stringRedisTemplate<span class="token punctuation">.</span><span class="token function">opsForValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span>LOGIN_CODE_KEY <span class="token operator">+</span> phone<span class="token punctuation">,</span> code<span class="token punctuation">,</span> LOGIN_CODE_TTL<span class="token punctuation">,</span> TimeUnit<span class="token punctuation">.</span>MINUTES<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 5.发送验证码</span>        log<span class="token punctuation">.</span><span class="token function">debug</span><span class="token punctuation">(</span><span class="token string">"发送短信验证码成功，验证码：{}"</span><span class="token punctuation">,</span> code<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 6.返回ok</span>        <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">ok</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> Result <span class="token function">login</span><span class="token punctuation">(</span>LoginFormDTO loginForm<span class="token punctuation">,</span> HttpSession session<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 1.校验手机号</span>        String phone <span class="token operator">=</span> loginForm<span class="token punctuation">.</span><span class="token function">getPhone</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>RegexUtils<span class="token punctuation">.</span><span class="token function">isPhoneInvalid</span><span class="token punctuation">(</span>phone<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 2.如果不符合，返回错误信息</span>            <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">fail</span><span class="token punctuation">(</span><span class="token string">"手机号格式错误！"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 3.从redis获取验证码并校验</span>        String cacheCode <span class="token operator">=</span> stringRedisTemplate<span class="token punctuation">.</span><span class="token function">opsForValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>LOGIN_CODE_KEY <span class="token operator">+</span> phone<span class="token punctuation">)</span><span class="token punctuation">;</span>        String code <span class="token operator">=</span> loginForm<span class="token punctuation">.</span><span class="token function">getCode</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>cacheCode <span class="token operator">==</span> null <span class="token operator">||</span> <span class="token operator">!</span>cacheCode<span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span>code<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 4.不一致，报错</span>            <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">fail</span><span class="token punctuation">(</span><span class="token string">"验证码错误"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 5.一致，根据手机号查询用户 select * from tb_user where phone = ?</span>        <span class="token comment" spellcheck="true">//User user = query().eq("phone", phone).one();</span>        LambdaQueryWrapper<span class="token operator">&lt;</span>User<span class="token operator">></span> lqw <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">LambdaQueryWrapper</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        User user <span class="token operator">=</span> <span class="token function">getOne</span><span class="token punctuation">(</span>lqw<span class="token punctuation">.</span><span class="token function">eq</span><span class="token punctuation">(</span>User<span class="token operator">:</span><span class="token operator">:</span>getPhone<span class="token punctuation">,</span> phone<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 6.判断用户是否存在</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>user <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">//不存在，则创建</span>            user <span class="token operator">=</span> <span class="token function">createUserWithPhone</span><span class="token punctuation">(</span>phone<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 7.保存用户信息到redis中</span>        <span class="token comment" spellcheck="true">// 7.1.随机生成token，作为登录令牌</span>        String token <span class="token operator">=</span> UUID<span class="token punctuation">.</span><span class="token function">randomUUID</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 7.2.将User对象转为HashMap存储</span>        UserDTO userDTO <span class="token operator">=</span> BeanUtil<span class="token punctuation">.</span><span class="token function">copyProperties</span><span class="token punctuation">(</span>user<span class="token punctuation">,</span> UserDTO<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        Map<span class="token operator">&lt;</span>String<span class="token punctuation">,</span> Object<span class="token operator">></span> userMap <span class="token operator">=</span> BeanUtil<span class="token punctuation">.</span><span class="token function">beanToMap</span><span class="token punctuation">(</span>userDTO<span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name">HashMap</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>                CopyOptions<span class="token punctuation">.</span><span class="token function">create</span><span class="token punctuation">(</span><span class="token punctuation">)</span>                        <span class="token punctuation">.</span><span class="token function">setIgnoreNullValue</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span>                        <span class="token punctuation">.</span><span class="token function">setFieldValueEditor</span><span class="token punctuation">(</span><span class="token punctuation">(</span>fieldName<span class="token punctuation">,</span> fieldValue<span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> fieldValue<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 7.3.存储用户信息</span>        String tokenKey <span class="token operator">=</span> LOGIN_USER_KEY <span class="token operator">+</span> token<span class="token punctuation">;</span>        stringRedisTemplate<span class="token punctuation">.</span><span class="token function">opsForHash</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">putAll</span><span class="token punctuation">(</span>tokenKey<span class="token punctuation">,</span> userMap<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 7.4.设置token有效期</span>        stringRedisTemplate<span class="token punctuation">.</span><span class="token function">expire</span><span class="token punctuation">(</span>tokenKey<span class="token punctuation">,</span> LOGIN_USER_TTL<span class="token punctuation">,</span> TimeUnit<span class="token punctuation">.</span>MINUTES<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 8.返回token</span>        <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">ok</span><span class="token punctuation">(</span>token<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">private</span> User <span class="token function">createUserWithPhone</span><span class="token punctuation">(</span>String phone<span class="token punctuation">)</span> <span class="token punctuation">{</span>        User user <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">User</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        user<span class="token punctuation">.</span><span class="token function">setPhone</span><span class="token punctuation">(</span>phone<span class="token punctuation">)</span><span class="token punctuation">;</span>        user<span class="token punctuation">.</span><span class="token function">setNickName</span><span class="token punctuation">(</span>USER_NICK_NAME_PREFIX <span class="token operator">+</span> RandomUtil<span class="token punctuation">.</span><span class="token function">randomNumbers</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token function">save</span><span class="token punctuation">(</span>user<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> user<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>校验登录，监听器代码</strong></p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">package</span> com<span class="token punctuation">.</span>kbdp<span class="token punctuation">.</span>utils<span class="token punctuation">;</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">LoginInterceptor</span> <span class="token keyword">implements</span> <span class="token class-name">HandlerInterceptor</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">//构造器注入（LoginInterceptor不是由Spring创建的）</span>    <span class="token keyword">private</span> StringRedisTemplate stringRedisTemplate<span class="token punctuation">;</span>    <span class="token keyword">public</span> <span class="token function">LoginInterceptor</span><span class="token punctuation">(</span>StringRedisTemplate stringRedisTemplate<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">this</span><span class="token punctuation">.</span>stringRedisTemplate <span class="token operator">=</span> stringRedisTemplate<span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> <span class="token keyword">boolean</span> <span class="token function">preHandle</span><span class="token punctuation">(</span>HttpServletRequest request<span class="token punctuation">,</span> HttpServletResponse response<span class="token punctuation">,</span> Object handler<span class="token punctuation">)</span> <span class="token keyword">throws</span> Exception <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 1.获取请求头中的token</span>        String token <span class="token operator">=</span> request<span class="token punctuation">.</span><span class="token function">getHeader</span><span class="token punctuation">(</span><span class="token string">"authorization"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>StrUtil<span class="token punctuation">.</span><span class="token function">isBlank</span><span class="token punctuation">(</span>token<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">//不存在，拦截，返回401状态码</span>            response<span class="token punctuation">.</span><span class="token function">setStatus</span><span class="token punctuation">(</span><span class="token number">401</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 2.基于TOKEN获取redis中的用户</span>        String key  <span class="token operator">=</span> LOGIN_USER_KEY <span class="token operator">+</span> token<span class="token punctuation">;</span>        Map<span class="token operator">&lt;</span>Object<span class="token punctuation">,</span> Object<span class="token operator">></span> userMap <span class="token operator">=</span> stringRedisTemplate<span class="token punctuation">.</span><span class="token function">opsForHash</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">entries</span><span class="token punctuation">(</span>key<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 3.判断用户是否存在</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>userMap<span class="token punctuation">.</span><span class="token function">isEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 4.不存在，拦截，返回401状态码</span>            response<span class="token punctuation">.</span><span class="token function">setStatus</span><span class="token punctuation">(</span><span class="token number">401</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 5.将查询到的hash数据转为UserDTO</span>        UserDTO userDTO <span class="token operator">=</span> BeanUtil<span class="token punctuation">.</span><span class="token function">fillBeanWithMap</span><span class="token punctuation">(</span>userMap<span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name">UserDTO</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">//6.存在，保存用户信息到Threadlocal</span>        UserHolder<span class="token punctuation">.</span><span class="token function">saveUser</span><span class="token punctuation">(</span>userDTO<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 7.刷新token有效期</span>        stringRedisTemplate<span class="token punctuation">.</span><span class="token function">expire</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> LOGIN_USER_TTL<span class="token punctuation">,</span> TimeUnit<span class="token punctuation">.</span>MINUTES<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 8.放行</span>        <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">package</span> com<span class="token punctuation">.</span>kbdp<span class="token punctuation">.</span>config<span class="token punctuation">;</span><span class="token annotation punctuation">@Configuration</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">MvcConfig</span> <span class="token keyword">implements</span> <span class="token class-name">WebMvcConfigurer</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">//注入StringRedisTemplate</span>    <span class="token annotation punctuation">@Resource</span>    <span class="token keyword">private</span> StringRedisTemplate stringRedisTemplate<span class="token punctuation">;</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">addInterceptors</span><span class="token punctuation">(</span>InterceptorRegistry registry<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 登录拦截器</span>        registry<span class="token punctuation">.</span><span class="token function">addInterceptor</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">LoginInterceptor</span><span class="token punctuation">(</span>stringRedisTemplate<span class="token punctuation">)</span><span class="token punctuation">)</span>                <span class="token punctuation">.</span><span class="token function">excludePathPatterns</span><span class="token punctuation">(</span>                        <span class="token string">"/shop/**"</span><span class="token punctuation">,</span>                        <span class="token string">"/voucher/**"</span><span class="token punctuation">,</span>                        <span class="token string">"/shop-type/**"</span><span class="token punctuation">,</span>                        <span class="token string">"/upload/**"</span><span class="token punctuation">,</span>                        <span class="token string">"/blog/hot"</span><span class="token punctuation">,</span>                        <span class="token string">"/user/code"</span><span class="token punctuation">,</span>                        <span class="token string">"/user/login"</span>                <span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="4-解决token刷新问题"><a href="#4-解决token刷新问题" class="headerlink" title="4.解决token刷新问题"></a>4.解决token刷新问题</h3><blockquote><p>即用户只要一直访问页面，那么就刷新token，既可以一直保持登录状态</p></blockquote><p><strong>初始方案：</strong></p><p>初始方案中，确实可以使用对应路径的拦截，同时刷新登录token令牌的存活时间，但是现在这个拦截器只是拦截需要被拦截的路径（登录才能访问的页面），假设当前用户访问了一些不需要拦截的路径，那么这个拦截器就不会生效，所以此时token令牌刷新的动作就不会执行，所以这个方案是存在问题的</p><p><img src="https://img.jwt1399.top/img/202210271640418.png"></p><p><strong>优化方案</strong></p><p>之前的拦截器无法对不需要拦截的路径生效，那么我们可以添加一个拦截器，在第一个拦截器中拦截所有的路径，把第二个拦截器做的事情放入到第一个拦截器中，同时刷新token令牌，因为第一个拦截器有了threadLocal的数据，所以此时第二个拦截器只需要判断拦截器中的user对象是否存在即可，完成整体刷新功能。</p><p><img src="https://img.jwt1399.top/img/202210271640150.png"></p><p><strong>RefreshTokenInterceptor</strong></p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">RefreshTokenInterceptor</span> <span class="token keyword">implements</span> <span class="token class-name">HandlerInterceptor</span> <span class="token punctuation">{</span>    <span class="token keyword">private</span> StringRedisTemplate stringRedisTemplate<span class="token punctuation">;</span>    <span class="token keyword">public</span> <span class="token function">RefreshTokenInterceptor</span><span class="token punctuation">(</span>StringRedisTemplate stringRedisTemplate<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">this</span><span class="token punctuation">.</span>stringRedisTemplate <span class="token operator">=</span> stringRedisTemplate<span class="token punctuation">;</span>    <span class="token punctuation">}</span>        <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> <span class="token keyword">boolean</span> <span class="token function">preHandle</span><span class="token punctuation">(</span>HttpServletRequest request<span class="token punctuation">,</span> HttpServletResponse response<span class="token punctuation">,</span> Object handler<span class="token punctuation">)</span> <span class="token keyword">throws</span> Exception <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 1.获取请求头中的token</span>        String token <span class="token operator">=</span> request<span class="token punctuation">.</span><span class="token function">getHeader</span><span class="token punctuation">(</span><span class="token string">"authorization"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>StrUtil<span class="token punctuation">.</span><span class="token function">isBlank</span><span class="token punctuation">(</span>token<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">{</span>            <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 2.基于TOKEN获取redis中的用户</span>        String key  <span class="token operator">=</span> LOGIN_USER_KEY <span class="token operator">+</span> token<span class="token punctuation">;</span>        Map<span class="token operator">&lt;</span>Object<span class="token punctuation">,</span> Object<span class="token operator">></span> userMap <span class="token operator">=</span> stringRedisTemplate<span class="token punctuation">.</span><span class="token function">opsForHash</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">entries</span><span class="token punctuation">(</span>key<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 3.判断用户是否存在</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>userMap<span class="token punctuation">.</span><span class="token function">isEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">{</span>            <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 4.将查询到的hash数据转为UserDTO</span>        UserDTO userDTO <span class="token operator">=</span> BeanUtil<span class="token punctuation">.</span><span class="token function">fillBeanWithMap</span><span class="token punctuation">(</span>userMap<span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name">UserDTO</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 5.存在，保存用户信息到Threadlocal</span>        UserHolder<span class="token punctuation">.</span><span class="token function">saveUser</span><span class="token punctuation">(</span>userDTO<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 6.刷新token有效期</span>        stringRedisTemplate<span class="token punctuation">.</span><span class="token function">expire</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> LOGIN_USER_TTL<span class="token punctuation">,</span> TimeUnit<span class="token punctuation">.</span>MINUTES<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 7.放行</span>        <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">afterCompletion</span><span class="token punctuation">(</span>HttpServletRequest request<span class="token punctuation">,</span> HttpServletResponse response<span class="token punctuation">,</span> Object handler<span class="token punctuation">,</span> Exception ex<span class="token punctuation">)</span> <span class="token keyword">throws</span> Exception <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 移除用户</span>        UserHolder<span class="token punctuation">.</span><span class="token function">removeUser</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span>    <span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>LoginInterceptor</strong></p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">LoginInterceptor</span> <span class="token keyword">implements</span> <span class="token class-name">HandlerInterceptor</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> <span class="token keyword">boolean</span> <span class="token function">preHandle</span><span class="token punctuation">(</span>HttpServletRequest request<span class="token punctuation">,</span> HttpServletResponse response<span class="token punctuation">,</span> Object handler<span class="token punctuation">)</span> <span class="token keyword">throws</span> Exception <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 1.判断是否需要拦截（ThreadLocal中是否有用户）</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>UserHolder<span class="token punctuation">.</span><span class="token function">getUser</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 没有用户，需要拦截，设置状态码</span>            response<span class="token punctuation">.</span><span class="token function">setStatus</span><span class="token punctuation">(</span><span class="token number">401</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">// 拦截</span>            <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 有用户，则放行</span>        <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>让拦截器生效</strong></p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">package</span> com<span class="token punctuation">.</span>kbdp<span class="token punctuation">.</span>config<span class="token punctuation">;</span><span class="token annotation punctuation">@Configuration</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">MvcConfig</span> <span class="token keyword">implements</span> <span class="token class-name">WebMvcConfigurer</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Resource</span>    <span class="token keyword">private</span> StringRedisTemplate stringRedisTemplate<span class="token punctuation">;</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">addInterceptors</span><span class="token punctuation">(</span>InterceptorRegistry registry<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 登录拦截器</span>        registry<span class="token punctuation">.</span><span class="token function">addInterceptor</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">LoginInterceptor</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>                <span class="token punctuation">.</span><span class="token function">excludePathPatterns</span><span class="token punctuation">(</span>                        <span class="token string">"/shop/**"</span><span class="token punctuation">,</span>                        <span class="token string">"/voucher/**"</span><span class="token punctuation">,</span>                        <span class="token string">"/shop-type/**"</span><span class="token punctuation">,</span>                        <span class="token string">"/upload/**"</span><span class="token punctuation">,</span>                        <span class="token string">"/blog/hot"</span><span class="token punctuation">,</span>                        <span class="token string">"/user/code"</span><span class="token punctuation">,</span>                        <span class="token string">"/user/login"</span>                <span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">order</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// token刷新的拦截器</span>        registry<span class="token punctuation">.</span><span class="token function">addInterceptor</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">RefreshTokenInterceptor</span><span class="token punctuation">(</span>stringRedisTemplate<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">addPathPatterns</span><span class="token punctuation">(</span><span class="token string">"/**"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">order</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">//order()的作用是设置优先级，越小越先执行</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="5-登出"><a href="#5-登出" class="headerlink" title="5.登出"></a>5.登出</h3><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Override</span><span class="token keyword">public</span> Result <span class="token function">logout</span><span class="token punctuation">(</span>HttpServletRequest httpServletRequest<span class="token punctuation">)</span> <span class="token punctuation">{</span>  String token <span class="token operator">=</span> httpServletRequest<span class="token punctuation">.</span><span class="token function">getHeader</span><span class="token punctuation">(</span><span class="token string">"authorization"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  String tokenKey <span class="token operator">=</span> LOGIN_USER_KEY <span class="token operator">+</span> token<span class="token punctuation">;</span>  <span class="token keyword">boolean</span> delete <span class="token operator">=</span> stringRedisTemplate<span class="token punctuation">.</span><span class="token function">delete</span><span class="token punctuation">(</span>tokenKey<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span>delete<span class="token punctuation">)</span><span class="token punctuation">{</span>    <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">ok</span><span class="token punctuation">(</span><span class="token string">"注销成功"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">}</span><span class="token keyword">else</span> <span class="token punctuation">{</span>    <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">ok</span><span class="token punctuation">(</span><span class="token string">"注销失败"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h1 id="②商户查询缓存"><a href="#②商户查询缓存" class="headerlink" title="②商户查询缓存"></a>②商户查询缓存</h1><h2 id="❶缓存简介"><a href="#❶缓存简介" class="headerlink" title="❶缓存简介"></a>❶缓存简介</h2><p><strong>缓存(<strong>Cache)，就是数据交换的</strong>缓冲区</strong>,俗称的缓存就是<strong>缓冲区内的数据</strong>,一般从数据库中获取,存储于本地代码(例如:</p><pre class="line-numbers language-java"><code class="language-java">例<span class="token number">1</span><span class="token operator">:</span>Static <span class="token keyword">final</span> ConcurrentHashMap<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> map <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ConcurrentHashMap</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> 本地用于高并发例<span class="token number">2</span><span class="token operator">:</span><span class="token keyword">static</span> <span class="token keyword">final</span> Cache<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> USER_CACHE <span class="token operator">=</span> CacheBuilder<span class="token punctuation">.</span><span class="token function">newBuilder</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> 用于redis等缓存例<span class="token number">3</span><span class="token operator">:</span>Static <span class="token keyword">final</span> Map<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> map <span class="token operator">=</span>  <span class="token keyword">new</span> <span class="token class-name">HashMap</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> 本地缓存<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>由于其被<strong>Static</strong>修饰，所以随着类的加载而被加载到<strong>内存之中</strong>，作为本地缓存，由于其又被<strong>final</strong>修饰，所以其引用(例3:map)和对象(例3:new HashMap())之间的关系是固定的，不能改变，因此不用担心赋值(&#x3D;)导致缓存失效;</p><p><strong>为什么要使用缓存</strong></p><p>缓存数据存储于代码中，而代码运行在内存中，内存的读写性能远高于磁盘，缓存可以大大降低<strong>用户访问并发量带来的</strong>服务器读写压力</p><table><thead><tr><th align="left">缓存优点</th><th>缓存缺点</th></tr></thead><tbody><tr><td align="left">降低后端负载 <br>提高读写效率，降低响应时间</td><td>数据一致性成本<br>代码维护成本<br>运维成本</td></tr></tbody></table><p><strong>如何使用缓存</strong></p><p>实际开发中，会构筑多级缓存来使系统运行速度进一步提升，例如：本地缓存与redis中的缓存并发使用</p><ul><li><p><strong>浏览器缓存</strong>：主要是存在于浏览器端的缓存</p></li><li><p><strong>应用层缓存：</strong>可以分为tomcat本地缓存，比如之前提到的map，或者是使用redis作为缓存</p></li><li><p><strong>数据库缓存：</strong>在数据库中有一片空间是 buffer pool，增改查数据都会先加载到mysql的缓存中</p></li><li><p><strong>CPU缓存：</strong>当代计算机最大的问题是 cpu性能提升了，但内存读写速度没有跟上，所以为了适应当下的情况，增加了cpu的L1，L2，L3级的缓存</p></li></ul><p><img src="/../images/Redis-%E5%AE%9E%E6%88%98%E7%AF%87/image-20220523212915666.png"></p><h2 id="❷添加商户缓存"><a href="#❷添加商户缓存" class="headerlink" title="❷添加商户缓存"></a>❷添加商户缓存</h2><p>在我们查询商户信息时，是直接从数据库中去进行查询的，直接查询数据库那肯定慢咯，所以我们需要增加缓存</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@GetMapping</span><span class="token punctuation">(</span><span class="token string">"/{id}"</span><span class="token punctuation">)</span><span class="token keyword">public</span> Result <span class="token function">queryShopById</span><span class="token punctuation">(</span><span class="token annotation punctuation">@PathVariable</span><span class="token punctuation">(</span><span class="token string">"id"</span><span class="token punctuation">)</span> Long id<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">//这里是直接查询数据库</span>    <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">ok</span><span class="token punctuation">(</span>shopService<span class="token punctuation">.</span><span class="token function">getById</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="1-缓存思路"><a href="#1-缓存思路" class="headerlink" title="1.缓存思路"></a>1.缓存思路</h3><p>标准的操作方式就是查询数据库之前先查询缓存，如果缓存数据存在，则直接从缓存中返回，如果缓存数据不存在，再查询数据库，然后将数据存入redis。</p><p><img src="https://img.jwt1399.top/img/202210311652776.png"></p><h3 id="2-代码实现"><a href="#2-代码实现" class="headerlink" title="2.代码实现"></a>2.代码实现</h3><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@GetMapping</span><span class="token punctuation">(</span><span class="token string">"/{id}"</span><span class="token punctuation">)</span><span class="token keyword">public</span> Result <span class="token function">queryShopById</span><span class="token punctuation">(</span><span class="token annotation punctuation">@PathVariable</span><span class="token punctuation">(</span><span class="token string">"id"</span><span class="token punctuation">)</span> Long id<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">//return Result.ok(shopService.getById(id)); //这里是直接查询数据库</span>    <span class="token keyword">return</span> shopService<span class="token punctuation">.</span><span class="token function">queryById</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//缓存实现</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Service</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ShopServiceImpl</span> <span class="token keyword">extends</span> <span class="token class-name">ServiceImpl</span><span class="token operator">&lt;</span>ShopMapper<span class="token punctuation">,</span> Shop<span class="token operator">></span> <span class="token keyword">implements</span> <span class="token class-name">IShopService</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Resource</span>    <span class="token keyword">private</span> StringRedisTemplate stringRedisTemplate<span class="token punctuation">;</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> Result <span class="token function">queryById</span><span class="token punctuation">(</span>Long id<span class="token punctuation">)</span> <span class="token punctuation">{</span>        String key <span class="token operator">=</span> CACHE_SHOP_KEY <span class="token operator">+</span> id<span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 1.从redis 查询商铺缓存</span>        String shopJson <span class="token operator">=</span> stringRedisTemplate<span class="token punctuation">.</span><span class="token function">opsForValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>key<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 2.判断是否存在</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>StrUtil<span class="token punctuation">.</span><span class="token function">isNotBlank</span><span class="token punctuation">(</span>shopJson<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 3.存在，直接返回</span>            Shop shop <span class="token operator">=</span> JSONUtil<span class="token punctuation">.</span><span class="token function">toBean</span><span class="token punctuation">(</span>shopJson<span class="token punctuation">,</span> Shop<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">ok</span><span class="token punctuation">(</span>shop<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 4.不存在，根据id查询数据库</span>        Shop shop <span class="token operator">=</span> <span class="token function">getById</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 5.数据库也不存在，返回错误</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>shop <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">fail</span><span class="token punctuation">(</span><span class="token string">"店铺不存在！"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 6.数据库存在，写入redis</span>        stringRedisTemplate<span class="token punctuation">.</span><span class="token function">opsForValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> JSONUtil<span class="token punctuation">.</span><span class="token function">toJsonStr</span><span class="token punctuation">(</span>shop<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 7.返回</span>        <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">ok</span><span class="token punctuation">(</span>shop<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="❸缓存更新策略"><a href="#❸缓存更新策略" class="headerlink" title="❸缓存更新策略"></a>❸缓存更新策略</h2><h3 id="1-更新策略"><a href="#1-更新策略" class="headerlink" title="1.更新策略"></a>1.更新策略</h3><p><strong>缓存更新</strong>是Redis为了节约内存而设计出来的一个东西，主要是因为内存数据宝贵，当我们向redis插入太多数据，此时就可能会导致缓存中的数据过多，所以redis会对部分数据进行更新。有三种更新方法：</p><ul><li><p><strong>内存淘汰：</strong>redis自动进行，当redis内存达到咱们设定的max-memery的时候，会自动触发淘汰机制，淘汰掉一些不重要的数据(可以自己设置策略方式)</p></li><li><p><strong>超时剔除：</strong>当我们给redis设置了过期时间ttl之后，redis会将超时的数据进行删除，方便继续使用缓存</p></li><li><p><strong>主动更新：</strong>我们可以手动调用方法把缓存删掉，通常用于解决缓存和数据库不一致问题</p></li></ul><p><img src="/../images/Redis-%E5%AE%9E%E6%88%98%E7%AF%87/1653322506393.png"></p><p><strong>数据不一致解决方案</strong></p><blockquote><p>由于我们的<strong>缓存的数据源来自于数据库</strong>，而数据库的<strong>数据是会发生变化的</strong>，因此，如果当数据库中<strong>数据发生变化，而缓存却没有同步</strong>，此时就会有<strong>一致性问题存在</strong>，其后果是：用户使用缓存中的过时数据，就会产生类似多线程数据安全问题，从而影响业务，产品口碑等；怎么解决呢？有如下几种方案</p></blockquote><ul><li><p>Cache Aside Pattern ：缓存调用者在更新完数据库后再去更新缓存，也称之为双写方案</p></li><li><p>Read&#x2F;Write Through Pattern : 由系统本身完成，数据库与缓存的问题交由系统本身去处理</p></li><li><p>Write Behind Caching Pattern ：调用者只操作缓存，其他线程去异步处理数据库，实现最终一致</p></li></ul><p>综合考虑使用方案一，但是操作缓存和数据库时有三个问题需要考虑：</p><p>如果采用第一个方案，那么假设我们每次操作数据库后，都操作缓存，但是中间如果没有人查询，那么这个更新动作实际上只有最后一次生效，中间的更新动作意义并不大，我们可以把缓存删除，等待再次查询时，将缓存中的数据加载出来</p><ul><li><p>1.删除缓存还是更新缓存？</p><ul><li>更新缓存：每次更新数据库都更新缓存，无效写操作较多</li><li>删除缓存：更新数据库时让缓存失效，查询时再更新缓存</li></ul></li><li><p>2.如何保证缓存与数据库的操作的同时成功或失败？</p><ul><li>单体系统，将缓存与数据库操作放在一个事务</li><li>分布式系统，利用TCC等分布式事务方案</li></ul></li><li><p>3.先操作缓存还是先操作数据库？</p><ul><li>先删除缓存，再操作数据库</li><li>先操作数据库，再删除缓存</li></ul></li></ul><p>我们应当是先操作数据库，再删除缓存，原因在于，如果你选择第一种方案，在两个线程并发来访问时，假设线程1先来，他先把缓存删了，此时线程2过来，他查询缓存数据并不存在，此时他写入缓存，当他写入缓存后，线程1再执行更新动作时，实际上写入的就是旧的数据，新的数据被旧数据覆盖了。</p><p><img src="/../images/Redis-%E5%AE%9E%E6%88%98%E7%AF%87/1653323595206.png"></p><h3 id="2-代码实现-1"><a href="#2-代码实现-1" class="headerlink" title="2.代码实现"></a>2.代码实现</h3><p>核心思路如下：</p><ul><li><p>1.根据id查询店铺时，如果缓存未命中，则查询数据库，将数据库结果写入缓存，并<strong>设置超时时间</strong></p></li><li><p>2.根据id修改店铺时，<strong>先修改数据库，再删除缓存</strong></p></li></ul><p><strong>1.设置redis缓存时添加过期时间</strong></p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Service</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ShopServiceImpl</span> <span class="token keyword">extends</span> <span class="token class-name">ServiceImpl</span><span class="token operator">&lt;</span>ShopMapper<span class="token punctuation">,</span> Shop<span class="token operator">></span> <span class="token keyword">implements</span> <span class="token class-name">IShopService</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Resource</span>    <span class="token keyword">private</span> StringRedisTemplate stringRedisTemplate<span class="token punctuation">;</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> Result <span class="token function">queryById</span><span class="token punctuation">(</span>Long id<span class="token punctuation">)</span> <span class="token punctuation">{</span>         <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>         <span class="token comment" spellcheck="true">// 6.数据库存在，写入redis，并添加过期时间</span>        stringRedisTemplate<span class="token punctuation">.</span><span class="token function">opsForValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> JSONUtil<span class="token punctuation">.</span><span class="token function">toJsonStr</span><span class="token punctuation">(</span>shop<span class="token punctuation">)</span><span class="token punctuation">,</span> CACHE_SHOP_TTL<span class="token punctuation">,</span> TimeUnit<span class="token punctuation">.</span>MINUTES<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>2.根据id修改店铺时，先修改数据库，再删除缓存</strong></p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Override</span><span class="token annotation punctuation">@Transactional</span><span class="token keyword">public</span> Result <span class="token function">update</span><span class="token punctuation">(</span>Shop shop<span class="token punctuation">)</span> <span class="token punctuation">{</span>    Long id <span class="token operator">=</span> shop<span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">if</span><span class="token punctuation">(</span>id <span class="token operator">==</span> null<span class="token punctuation">)</span><span class="token punctuation">{</span>      <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">fail</span><span class="token punctuation">(</span><span class="token string">"店铺id不能为空！"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">// 1.更新数据库</span>    <span class="token function">updateById</span><span class="token punctuation">(</span>shop<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 2.删除缓存</span>    stringRedisTemplate<span class="token punctuation">.</span><span class="token function">delete</span><span class="token punctuation">(</span>CACHE_SHOP_KEY <span class="token operator">+</span> id<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">ok</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>代码分析：当我们修改了数据之后，然后把缓存中的数据进行删除，查询时发现缓存中没有数据，则会从mysql中加载最新的数据，从而避免数据库和缓存不一致的问题。</p><h2 id="❹缓存穿透"><a href="#❹缓存穿透" class="headerlink" title="❹缓存穿透"></a>❹缓存穿透</h2><blockquote><p>缓存穿透是指客户端请求的<strong>数据在缓存中和数据库中都不存在</strong>，这样缓存永远不会生效，这些请求都会打到数据库。</p></blockquote><h3 id="1-解决思路"><a href="#1-解决思路" class="headerlink" title="1.解决思路"></a>1.解决思路</h3><p>缓存穿透的解决方案有哪些？</p><ul><li>缓存null值</li><li>布隆过滤</li><li>增强id的复杂度，避免被猜测id规律</li><li>做好数据的基础格式校验</li><li>加强用户权限校验</li><li>做好热点参数的限流</li></ul><p>常见的解决方案有两种：</p><table><thead><tr><th align="center"></th><th>缓存空对象</th><th>布隆过滤器</th></tr></thead><tbody><tr><td align="center">优点</td><td>实现简单，维护方便</td><td>内存占用较少，没有多余key</td></tr><tr><td align="center">缺点</td><td>额外的内存消耗<br>可能造成短期的不一致</td><td>实现复杂<br/>存在误判可能</td></tr></tbody></table><p><strong>缓存空对象：</strong>哪怕这个数据在数据库中不存在，我们也把这个数据存入到redis中去，这样，下次用户过来访问这个不存在的数据，那么在redis中也能找到这个数据</p><p><strong>布隆过滤器：</strong>通过一个庞大的二进制数组，走哈希思想去判断当前这个要查询的这个数据是否存在，如果布隆过滤器判断存在，则放行；不存在，则直接返回</p><p>这种方式优点在于节约内存空间，但存在误判，误判原因在于：布隆过滤器走的是哈希思想，可能存在哈希冲突</p><p><img src="https://img.jwt1399.top/img/202211021526445.png"></p><h3 id="2-具体实现"><a href="#2-具体实现" class="headerlink" title="2.具体实现"></a>2.具体实现</h3><p>原来的逻辑中，如果发现这个数据在mysql中不存在，直接就返回404了，这样是会存在缓存穿透问题的</p><p>现在的逻辑中：如果这个数据不存在，我们不会返回404 ，还是会把这个数据写入到Redis中，并且将value设置为空，欧当再次发起查询时，我们如果发现命中之后，判断这个value是否是null，如果是null，则是之前写入的数据，证明是缓存穿透数据，如果不是，则直接返回数据。</p><p><img src="https://img.jwt1399.top/img/202211021536810.png"></p><pre class="line-numbers language-java"><code class="language-java">        <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> Result <span class="token function">queryById</span><span class="token punctuation">(</span>Long id<span class="token punctuation">)</span> <span class="token punctuation">{</span>        String key <span class="token operator">=</span> CACHE_SHOP_KEY <span class="token operator">+</span> id<span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 1.从redis 查询商铺缓存</span>        String shopJson <span class="token operator">=</span> stringRedisTemplate<span class="token punctuation">.</span><span class="token function">opsForValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>key<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 2.判断是否存在（3种情况，存在不为空，存在但为""，不存在为null）</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>StrUtil<span class="token punctuation">.</span><span class="token function">isNotBlank</span><span class="token punctuation">(</span>shopJson<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 3.存在，直接返回</span>            Shop shop <span class="token operator">=</span> JSONUtil<span class="token punctuation">.</span><span class="token function">toBean</span><span class="token punctuation">(</span>shopJson<span class="token punctuation">,</span> Shop<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">ok</span><span class="token punctuation">(</span>shop<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">//判断命中的是否是空值(区别null和"")</span>        <span class="token comment" spellcheck="true">/*新增语句*/</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token string">""</span><span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span>shopJson<span class="token punctuation">)</span><span class="token punctuation">{</span>          <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">fail</span><span class="token punctuation">(</span><span class="token string">"店铺信息不存在！"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>              <span class="token comment" spellcheck="true">// 4.不存在，根据id查询数据库</span>        Shop shop <span class="token operator">=</span> <span class="token function">getById</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 5.数据库也不存在，返回错误</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>shop <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">/*新增语句*/</span>            <span class="token comment" spellcheck="true">//不存在时存入空值</span>            stringRedisTemplate<span class="token punctuation">.</span><span class="token function">opsForValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span><span class="token string">""</span><span class="token punctuation">,</span>CACHE_NULL_TTL<span class="token punctuation">,</span>TimeUnit<span class="token punctuation">.</span>MINUTES<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">fail</span><span class="token punctuation">(</span><span class="token string">"店铺不存在！"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 6.数据库存在，写入redis</span>        stringRedisTemplate<span class="token punctuation">.</span><span class="token function">opsForValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> JSONUtil<span class="token punctuation">.</span><span class="token function">toJsonStr</span><span class="token punctuation">(</span>shop<span class="token punctuation">)</span><span class="token punctuation">,</span> CACHE_SHOP_TTL<span class="token punctuation">,</span> TimeUnit<span class="token punctuation">.</span>MINUTES<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 7.返回</span>        <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">ok</span><span class="token punctuation">(</span>shop<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>分析：当查询的数据在数据库和Redis中都不存在时，会在Redis中存入空值，并返回“店铺不存在！”，当再次查询该数据就不再经过数据库，而是去Redis中取，并返回“店铺信息不存在！”</p><h2 id="❺缓存雪崩"><a href="#❺缓存雪崩" class="headerlink" title="❺缓存雪崩"></a>❺缓存雪崩</h2><blockquote><p>缓存雪崩是指在<strong>同一时段大量的缓存key同时失效或者Redis服务宕机</strong>，导致大量请求到达数据库，带来巨大压力。</p></blockquote><h3 id="解决思路"><a href="#解决思路" class="headerlink" title="解决思路"></a>解决思路</h3><ul><li>给不同的Key的TTL添加随机值</li><li>利用Redis集群提高服务的可用性</li><li>给缓存业务添加降级限流策略</li><li>给业务添加多级缓存</li></ul><h2 id="❻缓存击穿"><a href="#❻缓存击穿" class="headerlink" title="❻缓存击穿"></a>❻缓存击穿</h2><blockquote><p>缓存击穿问题也叫热点Key问题，指<strong>一个被高并发访问并且缓存重建业务较复杂的key突然失效了</strong>，无数的请求访问会在瞬间给数据库带来巨大的冲击。</p></blockquote><h3 id="1-解决思路-1"><a href="#1-解决思路-1" class="headerlink" title="1.解决思路"></a>1.解决思路</h3><p>常见的解决方案有两种：</p><ul><li><strong>互斥锁</strong></li></ul><p>因为锁能实现互斥性。假设线程过来，只能逐一的访问数据库，从而避免数据库访问压力过大，但这也会影响查询性能，因为此时会让查询的性能从并行变成了串行，我们可以采用tryLock方法 + double check来解决这样的问题。</p><ul><li><strong>逻辑过期</strong></li></ul><p>之所以会出现这个缓存击穿问题，主要原因是在于我们对key设置了过期时间，假设我们不设置过期时间，其实就不会有缓存击穿的问题，但是不设置过期时间，这样数据不就一直占用我们内存了吗，可以采用逻辑过期方案。</p><p>这种方案巧妙在于，异步的构建缓存，缺点在于在构建完缓存之前，返回的都是脏数据。</p><table><thead><tr><th align="center">互斥锁</th><th align="center">逻辑过期</th></tr></thead><tbody><tr><td align="center"><img src="https://img.jwt1399.top/img/202211021615160.png"></td><td align="center"><img src="https://img.jwt1399.top/img/202211021616600.png"></td></tr><tr><td align="center">假设现在线程1过来访问，他查询缓存没有命中，但是此时他获得到了锁的资源，那么线程1就会一个人去执行逻辑，假设现在线程2过来，线程2在执行过程中，并没有获得到锁，那么线程2就可以进行到休眠，直到线程1把锁释放后，线程2获得到锁，然后再来执行逻辑，此时就能够从缓存中拿到数据了。</td><td align="center">把过期时间设置在redis的value中，这个过期时间并不会直接作用于redis，而是后续通过逻辑去处理。假设线程1去查询缓存，然后从value中判断出来当前的数据已经过期，此时线程1去获得互斥锁，那么其他线程会进行阻塞，获得了锁的线程会开启一个 线程去进行以前的重构数据的逻辑，直到新开的线程完成这个逻辑后，才释放锁， 而线程1直接进行返回，假设现在线程3过来访问，由于线程线程2持有着锁，所以线程3无法获得锁，线程3也直接返回数据，只有等到新开的线程2把重建数据构建完后，其他线程才能走返回正确的数据。</td></tr></tbody></table><p><img src="https://img.jwt1399.top/img/202211021615745.png"></p><h3 id="2-具体实现-1"><a href="#2-具体实现-1" class="headerlink" title="2.具体实现"></a>2.具体实现</h3><h4 id="互斥锁实现"><a href="#互斥锁实现" class="headerlink" title="互斥锁实现"></a><strong>互斥锁实现</strong></h4><p>核心思路：相较于原来从缓存中查询不到数据后直接查询数据库而言，现在的方案是进行查询之后，如果从缓存没有查询到数据，则进行互斥锁的获取，获取互斥锁后，判断是否得到了锁，如果没有得到，则休眠，过一会再进行尝试，直到获取到锁为止，才能进行查询，查询后将数据写入redis，再释放锁，返回数据，利用互斥锁就能保证只有一个线程去执行操作数据库的逻辑，防止缓存击穿。</p><img src="https://img.jwt1399.top/img/202211201526307.png" style="zoom:50%;" /><p><strong>操作锁的代码：</strong></p><p>思路：利用redis的<code>setnx</code>方法来表示获取锁，该方法含义是redis中：</p><ul><li><p>如果没有这个key，则插入成功，返回1，在stringRedisTemplate中返回true </p></li><li><p>如果有这个key，则插入失败，返回0，在stringRedisTemplate返回false</p></li></ul><p>通过true或false，来表示是否有线程成功插入key，成功插入key的线程就是得到锁的线程。</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">boolean</span> <span class="token function">tryLock</span><span class="token punctuation">(</span>String key<span class="token punctuation">)</span> <span class="token punctuation">{</span>    Boolean flag <span class="token operator">=</span> stringRedisTemplate<span class="token punctuation">.</span><span class="token function">opsForValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">setIfAbsent</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> <span class="token string">"1"</span><span class="token punctuation">,</span> <span class="token number">10</span><span class="token punctuation">,</span> TimeUnit<span class="token punctuation">.</span>SECONDS<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> BooleanUtil<span class="token punctuation">.</span><span class="token function">isTrue</span><span class="token punctuation">(</span>flag<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">unlock</span><span class="token punctuation">(</span>String key<span class="token punctuation">)</span> <span class="token punctuation">{</span>    stringRedisTemplate<span class="token punctuation">.</span><span class="token function">delete</span><span class="token punctuation">(</span>key<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>互斥锁防止缓存击穿代码：</strong></p><pre class="line-numbers language-java"><code class="language-java"> <span class="token keyword">public</span> Shop <span class="token function">queryWithMutex</span><span class="token punctuation">(</span>Long id<span class="token punctuation">)</span>  <span class="token punctuation">{</span>        String key <span class="token operator">=</span> CACHE_SHOP_KEY <span class="token operator">+</span> id<span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 1、从redis中查询商铺缓存</span>        String shopJson <span class="token operator">=</span> stringRedisTemplate<span class="token punctuation">.</span><span class="token function">opsForValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>key<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 2、判断是否存在</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>StrUtil<span class="token punctuation">.</span><span class="token function">isNotBlank</span><span class="token punctuation">(</span>shopJson<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 存在,直接返回</span>            <span class="token keyword">return</span> JSONUtil<span class="token punctuation">.</span><span class="token function">toBean</span><span class="token punctuation">(</span>shopJson<span class="token punctuation">,</span> Shop<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">//判断命中的值是否是空值</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>shopJson <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">//返回一个错误信息</span>            <span class="token keyword">return</span> null<span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 4.实现缓存重构</span>        <span class="token comment" spellcheck="true">//4.1 获取互斥锁</span>        String lockKey <span class="token operator">=</span> <span class="token string">"lock:shop:"</span> <span class="token operator">+</span> id<span class="token punctuation">;</span>        Shop shop <span class="token operator">=</span> null<span class="token punctuation">;</span>        <span class="token keyword">try</span> <span class="token punctuation">{</span>            <span class="token keyword">boolean</span> isLock <span class="token operator">=</span> <span class="token function">tryLock</span><span class="token punctuation">(</span>lockKey<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">// 4.2 判断否获取成功</span>            <span class="token keyword">if</span><span class="token punctuation">(</span><span class="token operator">!</span>isLock<span class="token punctuation">)</span><span class="token punctuation">{</span>                <span class="token comment" spellcheck="true">//4.3 失败，则休眠重试</span>                Thread<span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">50</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token keyword">return</span> <span class="token function">queryWithMutex</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>            <span class="token comment" spellcheck="true">//4.4 成功，根据id查询数据库</span>             shop <span class="token operator">=</span> <span class="token function">getById</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">// 5.不存在，返回错误</span>            <span class="token keyword">if</span><span class="token punctuation">(</span>shop <span class="token operator">==</span> null<span class="token punctuation">)</span><span class="token punctuation">{</span>                 <span class="token comment" spellcheck="true">//将空值写入redis</span>stringRedisTemplate<span class="token punctuation">.</span><span class="token function">opsForValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span><span class="token string">""</span><span class="token punctuation">,</span>CACHE_NULL_TTL<span class="token punctuation">,</span>TimeUnit<span class="token punctuation">.</span>MINUTES<span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token comment" spellcheck="true">//返回错误信息</span>                <span class="token keyword">return</span> null<span class="token punctuation">;</span>            <span class="token punctuation">}</span>            <span class="token comment" spellcheck="true">//6.写入redis</span>            stringRedisTemplate<span class="token punctuation">.</span><span class="token function">opsForValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span>JSONUtil<span class="token punctuation">.</span><span class="token function">toJsonStr</span><span class="token punctuation">(</span>shop<span class="token punctuation">)</span><span class="token punctuation">,</span>CACHE_SHOP_TTL<span class="token punctuation">,</span>TimeUnit<span class="token punctuation">.</span>MINUTES<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span><span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Exception</span> e<span class="token punctuation">)</span><span class="token punctuation">{</span>            <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">RuntimeException</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">finally</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">//7.释放互斥锁</span>            <span class="token function">unlock</span><span class="token punctuation">(</span>lockKey<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> shop<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>调用代码</strong></p><pre class="line-numbers language-java"><code class="language-java">    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> Result <span class="token function">queryById</span><span class="token punctuation">(</span>Long id<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 缓存穿透</span>        <span class="token comment" spellcheck="true">//Shop shop = queryWithPassThrough(id);</span>         <span class="token comment" spellcheck="true">// 互斥锁解决缓存击穿</span>        Shop shop <span class="token operator">=</span> <span class="token function">queryWithMutex</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>shop <span class="token operator">==</span> null<span class="token punctuation">)</span><span class="token punctuation">{</span>            <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">fail</span><span class="token punctuation">(</span><span class="token string">"店铺不存在!!!"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">ok</span><span class="token punctuation">(</span>shop<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>分析：假设现在有1000个请求同时访问id&#x3D;1的数据，并且该数据已经失效，这1000个请求都会成功。</p><h4 id="逻辑过期实现"><a href="#逻辑过期实现" class="headerlink" title="逻辑过期实现"></a><strong>逻辑过期实现</strong></h4><p>思路：当用户开始查询redis时，判断是否命中，如果没有命中则直接返回空数据，不查询数据库，而一旦命中后，将value取出，判断value中是否过期，如果没有过期，则直接返回redis中的数据；如果过期，则在开启独立线程后直接返回之前的数据，独立线程去重构数据，重构完成后释放互斥锁。</p><img src="https://img.jwt1399.top/img/202211201541199.png" style="zoom:50%;" /><p><strong>步骤一：设置逻辑过期时间</strong></p><p>因为现在redis中存储数据的value需带上逻辑过期时间</p><ul><li>1.此时要么去修改原来Shop实体类</li><li>2.要么新建一个实体类</li></ul><p>我们采用第二个方案，对原来代码没有侵入性。</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Data</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">RedisData</span> <span class="token punctuation">{</span>    <span class="token keyword">private</span> LocalDateTime expireTime<span class="token punctuation">;</span>    <span class="token keyword">private</span> Object data<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤二：预热数据</strong></p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">saveShop2Redis</span><span class="token punctuation">(</span>Long id<span class="token punctuation">,</span> Long expireSeconds<span class="token punctuation">)</span> <span class="token punctuation">{</span>  <span class="token comment" spellcheck="true">// 1.查询店铺数据</span>  Shop shop <span class="token operator">=</span> <span class="token function">getById</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">;</span>  Thread<span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">200</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//模拟延迟</span>  <span class="token comment" spellcheck="true">// 2.封装逻辑过期时间</span>  RedisData redisData <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">RedisData</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  redisData<span class="token punctuation">.</span><span class="token function">setData</span><span class="token punctuation">(</span>shop<span class="token punctuation">)</span><span class="token punctuation">;</span>  redisData<span class="token punctuation">.</span><span class="token function">setExpireTime</span><span class="token punctuation">(</span>LocalDateTime<span class="token punctuation">.</span><span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">plusSeconds</span><span class="token punctuation">(</span>expireSeconds<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment" spellcheck="true">//3.写入Redis</span>  stringRedisTemplate<span class="token punctuation">.</span><span class="token function">opsForValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span>CACHE_SHOP_KEY <span class="token operator">+</span> id<span class="token punctuation">,</span> JSONUtil<span class="token punctuation">.</span><span class="token function">toJsonStr</span><span class="token punctuation">(</span>redisData<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>利用单元测试进行缓存预热</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Resource</span><span class="token keyword">private</span> ShopServiceImpl shopService<span class="token punctuation">;</span><span class="token annotation punctuation">@Test</span><span class="token keyword">void</span> <span class="token function">testSaveShop</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>  shopService<span class="token punctuation">.</span><span class="token function">saveShop2Redis</span><span class="token punctuation">(</span>1L<span class="token punctuation">,</span> 10L<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>逻辑过期防止缓存击穿代码</strong></p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> ExecutorService CACHE_REBUILD_EXECUTOR <span class="token operator">=</span> Executors<span class="token punctuation">.</span><span class="token function">newFixedThreadPool</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 线程池</span><span class="token keyword">public</span> Shop <span class="token function">queryWithLogicalExpire</span><span class="token punctuation">(</span> Long id <span class="token punctuation">)</span> <span class="token punctuation">{</span>    String key <span class="token operator">=</span> CACHE_SHOP_KEY <span class="token operator">+</span> id<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 1.从redis查询商铺缓存</span>    String json <span class="token operator">=</span> stringRedisTemplate<span class="token punctuation">.</span><span class="token function">opsForValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>key<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 2.判断是否存在</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>StrUtil<span class="token punctuation">.</span><span class="token function">isBlank</span><span class="token punctuation">(</span>json<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 3.不存在，直接返回</span>        <span class="token keyword">return</span> null<span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">// 4.存在，需要先把json反序列化为对象</span>    RedisData redisData <span class="token operator">=</span> JSONUtil<span class="token punctuation">.</span><span class="token function">toBean</span><span class="token punctuation">(</span>json<span class="token punctuation">,</span> RedisData<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    Shop shop <span class="token operator">=</span> JSONUtil<span class="token punctuation">.</span><span class="token function">toBean</span><span class="token punctuation">(</span><span class="token punctuation">(</span>JSONObject<span class="token punctuation">)</span> redisData<span class="token punctuation">.</span><span class="token function">getData</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> Shop<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    LocalDateTime expireTime <span class="token operator">=</span> redisData<span class="token punctuation">.</span><span class="token function">getExpireTime</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 5.判断是否过期</span>    <span class="token keyword">if</span><span class="token punctuation">(</span>expireTime<span class="token punctuation">.</span><span class="token function">isAfter</span><span class="token punctuation">(</span>LocalDateTime<span class="token punctuation">.</span><span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 5.1.未过期，直接返回店铺信息</span>        <span class="token keyword">return</span> shop<span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">// 5.2.已过期，需要缓存重建</span>    <span class="token comment" spellcheck="true">// 5.2.1.获取互斥锁</span>    String lockKey <span class="token operator">=</span> LOCK_SHOP_KEY <span class="token operator">+</span> id<span class="token punctuation">;</span>    <span class="token keyword">boolean</span> isLock <span class="token operator">=</span> <span class="token function">tryLock</span><span class="token punctuation">(</span>lockKey<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 5.2.2.判断是否获取锁成功</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>isLock<span class="token punctuation">)</span><span class="token punctuation">{</span>        CACHE_REBUILD_EXECUTOR<span class="token punctuation">.</span><span class="token function">submit</span><span class="token punctuation">(</span> <span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">></span><span class="token punctuation">{</span>            <span class="token keyword">try</span><span class="token punctuation">{</span>                <span class="token comment" spellcheck="true">//5.2.3.重建缓存</span>                <span class="token function">saveShop2Redis</span><span class="token punctuation">(</span>id<span class="token punctuation">,</span> 20L<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//实际中应设置成30分钟，这里是为了测试</span>            <span class="token punctuation">}</span><span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Exception</span> e<span class="token punctuation">)</span><span class="token punctuation">{</span>                <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">RuntimeException</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span><span class="token keyword">finally</span> <span class="token punctuation">{</span>                <span class="token function">unlock</span><span class="token punctuation">(</span>lockKey<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">// 5.2.4.返回过期的商铺信息</span>    <span class="token keyword">return</span> shop<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p> 注意：获取锁成功应该再次检测redis缓存是否过期，做DoubleCheck。如果存在则无需重建缓存。</p><p>分析：假设现在有100个请求同时访问id&#x3D;1的数据，并且该数据在数据库中已经被修改，但是Redis中没有被修改，这100个请求都会成功，只是前面的请求都是Redis中没修改的脏数据，后面的请求是Redis重建后的数据。</p><p>也就是说请求来了先把Redis中逻辑过期的数据先返回给用户，等某个请求拿到锁之后重建缓存（访问一次数据库），之后就返回重建的新数据</p><h2 id="❼封装Redis工具类"><a href="#❼封装Redis工具类" class="headerlink" title="❼封装Redis工具类"></a>❼封装Redis工具类</h2><p>基于StringRedisTemplate封装一个缓存工具类CacheClient</p><p>满足下列需求：</p><ul><li>方法1：将任意Java对象序列化为json并存储在string类型的key中，并且可以设置TTL过期时间</li><li>方法2：将任意Java对象序列化为json并存储在string类型的key中，并且可以设置逻辑过期时间</li><li>方法3：根据指定的key查询缓存，并反序列化为指定类型，利用缓存空值的方式解决缓存穿透问题</li><li>方法4：根据指定的key查询缓存，并反序列化为指定类型，需要利用逻辑过期解决缓存击穿问题</li><li>方法5：根据指定的key查询缓存，并反序列化为指定类型，需要利用互斥锁解决缓存击穿问题</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Slf4j</span><span class="token annotation punctuation">@Component</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">CacheClient</span> <span class="token punctuation">{</span>    <span class="token keyword">private</span> <span class="token keyword">final</span> StringRedisTemplate stringRedisTemplate<span class="token punctuation">;</span>    <span class="token keyword">public</span> <span class="token function">CacheClient</span><span class="token punctuation">(</span>StringRedisTemplate stringRedisTemplate<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">this</span><span class="token punctuation">.</span>stringRedisTemplate <span class="token operator">=</span> stringRedisTemplate<span class="token punctuation">;</span>    <span class="token punctuation">}</span>        <span class="token keyword">private</span> <span class="token keyword">boolean</span> <span class="token function">tryLock</span><span class="token punctuation">(</span>String key<span class="token punctuation">)</span> <span class="token punctuation">{</span>      Boolean flag <span class="token operator">=</span> stringRedisTemplate<span class="token punctuation">.</span><span class="token function">opsForValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">setIfAbsent</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> <span class="token string">"1"</span><span class="token punctuation">,</span> <span class="token number">10</span><span class="token punctuation">,</span> TimeUnit<span class="token punctuation">.</span>SECONDS<span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token keyword">return</span> BooleanUtil<span class="token punctuation">.</span><span class="token function">isTrue</span><span class="token punctuation">(</span>flag<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">unlock</span><span class="token punctuation">(</span>String key<span class="token punctuation">)</span> <span class="token punctuation">{</span>      stringRedisTemplate<span class="token punctuation">.</span><span class="token function">delete</span><span class="token punctuation">(</span>key<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">// 方法1：set()</span>    <span class="token comment" spellcheck="true">// 方法2：setWithLogicalExpire()</span>    <span class="token comment" spellcheck="true">// 方法3：queryWithPassThrough()</span>    <span class="token comment" spellcheck="true">// 方法4：queryWithLogicalExpire()</span>    <span class="token comment" spellcheck="true">// 方法5：queryWithMutex()</span>  <span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>方法1：将任意Java对象序列化为json并存储在string类型的key中，并且可以设置TTL过期时间</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">set</span><span class="token punctuation">(</span>String key<span class="token punctuation">,</span> Object value<span class="token punctuation">,</span> Long time<span class="token punctuation">,</span> TimeUnit unit<span class="token punctuation">)</span> <span class="token punctuation">{</span>  stringRedisTemplate<span class="token punctuation">.</span><span class="token function">opsForValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> JSONUtil<span class="token punctuation">.</span><span class="token function">toJsonStr</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span><span class="token punctuation">,</span> time<span class="token punctuation">,</span> unit<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><ul><li>方法2：将任意Java对象序列化为json并存储在string类型的key中，并且可以设置逻辑过期时间</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">setWithLogicalExpire</span><span class="token punctuation">(</span>String key<span class="token punctuation">,</span> Object value<span class="token punctuation">,</span> Long time<span class="token punctuation">,</span> TimeUnit unit<span class="token punctuation">)</span> <span class="token punctuation">{</span>  <span class="token comment" spellcheck="true">// 设置逻辑过期</span>  RedisData redisData <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">RedisData</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  redisData<span class="token punctuation">.</span><span class="token function">setData</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span><span class="token punctuation">;</span>  redisData<span class="token punctuation">.</span><span class="token function">setExpireTime</span><span class="token punctuation">(</span>LocalDateTime<span class="token punctuation">.</span><span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">plusSeconds</span><span class="token punctuation">(</span>unit<span class="token punctuation">.</span><span class="token function">toSeconds</span><span class="token punctuation">(</span>time<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment" spellcheck="true">// 写入Redis</span>  stringRedisTemplate<span class="token punctuation">.</span><span class="token function">opsForValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> JSONUtil<span class="token punctuation">.</span><span class="token function">toJsonStr</span><span class="token punctuation">(</span>redisData<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>方法3：根据指定的key查询缓存，并反序列化为指定类型，利用缓存空值的方式解决缓存穿透问题</li></ul><pre class="line-numbers language-java"><code class="language-java"> <span class="token keyword">public</span> <span class="token operator">&lt;</span>R<span class="token punctuation">,</span>ID<span class="token operator">></span> R <span class="token function">queryWithPassThrough</span><span class="token punctuation">(</span>String keyPrefix<span class="token punctuation">,</span> ID id<span class="token punctuation">,</span> Class<span class="token operator">&lt;</span>R<span class="token operator">></span> type<span class="token punctuation">,</span> Function<span class="token operator">&lt;</span>ID<span class="token punctuation">,</span> R<span class="token operator">></span> dbFallback<span class="token punctuation">,</span> Long time<span class="token punctuation">,</span> TimeUnit unit<span class="token punctuation">)</span><span class="token punctuation">{</span>        String key <span class="token operator">=</span> keyPrefix <span class="token operator">+</span> id<span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 1.从redis查询商铺缓存</span>        String json <span class="token operator">=</span> stringRedisTemplate<span class="token punctuation">.</span><span class="token function">opsForValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>key<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 2.判断是否存在</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>StrUtil<span class="token punctuation">.</span><span class="token function">isNotBlank</span><span class="token punctuation">(</span>json<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 3.存在，直接返回</span>            <span class="token keyword">return</span> JSONUtil<span class="token punctuation">.</span><span class="token function">toBean</span><span class="token punctuation">(</span>json<span class="token punctuation">,</span> type<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 判断命中的是否是空值</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>json <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 返回一个错误信息</span>            <span class="token keyword">return</span> null<span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 4.不存在，根据id查询数据库</span>        R r <span class="token operator">=</span> dbFallback<span class="token punctuation">.</span><span class="token function">apply</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 5.不存在，返回错误</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>r <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 将空值写入redis</span>            stringRedisTemplate<span class="token punctuation">.</span><span class="token function">opsForValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> <span class="token string">""</span><span class="token punctuation">,</span> CACHE_NULL_TTL<span class="token punctuation">,</span> TimeUnit<span class="token punctuation">.</span>MINUTES<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">// 返回错误信息</span>            <span class="token keyword">return</span> null<span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 6.存在，写入redis</span>        <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> r<span class="token punctuation">,</span> time<span class="token punctuation">,</span> unit<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> r<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>方法4：根据指定的key查询缓存，并反序列化为指定类型，需要利用逻辑过期解决缓存击穿问题</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> ExecutorService CACHE_REBUILD_EXECUTOR <span class="token operator">=</span> Executors<span class="token punctuation">.</span><span class="token function">newFixedThreadPool</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">public</span> <span class="token operator">&lt;</span>R<span class="token punctuation">,</span> ID<span class="token operator">></span> R <span class="token function">queryWithLogicalExpire</span><span class="token punctuation">(</span>String keyPrefix<span class="token punctuation">,</span> ID id<span class="token punctuation">,</span> Class<span class="token operator">&lt;</span>R<span class="token operator">></span> type<span class="token punctuation">,</span> Function<span class="token operator">&lt;</span>ID<span class="token punctuation">,</span> R<span class="token operator">></span> dbFallback<span class="token punctuation">,</span> Long time<span class="token punctuation">,</span> TimeUnit unit<span class="token punctuation">)</span> <span class="token punctuation">{</span>        String key <span class="token operator">=</span> keyPrefix <span class="token operator">+</span> id<span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 1.从redis查询商铺缓存</span>        String json <span class="token operator">=</span> stringRedisTemplate<span class="token punctuation">.</span><span class="token function">opsForValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>key<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 2.判断是否存在</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>StrUtil<span class="token punctuation">.</span><span class="token function">isBlank</span><span class="token punctuation">(</span>json<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 3.存在，直接返回</span>            <span class="token keyword">return</span> null<span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 4.命中，需要先把json反序列化为对象</span>        RedisData redisData <span class="token operator">=</span> JSONUtil<span class="token punctuation">.</span><span class="token function">toBean</span><span class="token punctuation">(</span>json<span class="token punctuation">,</span> RedisData<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        R r <span class="token operator">=</span> JSONUtil<span class="token punctuation">.</span><span class="token function">toBean</span><span class="token punctuation">(</span><span class="token punctuation">(</span>JSONObject<span class="token punctuation">)</span> redisData<span class="token punctuation">.</span><span class="token function">getData</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> type<span class="token punctuation">)</span><span class="token punctuation">;</span>        LocalDateTime expireTime <span class="token operator">=</span> redisData<span class="token punctuation">.</span><span class="token function">getExpireTime</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 5.判断是否过期</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>expireTime<span class="token punctuation">.</span><span class="token function">isAfter</span><span class="token punctuation">(</span>LocalDateTime<span class="token punctuation">.</span><span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 5.1.未过期，直接返回店铺信息</span>            <span class="token keyword">return</span> r<span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 5.2.已过期，需要缓存重建</span>        <span class="token comment" spellcheck="true">// 6.缓存重建</span>        <span class="token comment" spellcheck="true">// 6.1.获取互斥锁</span>        String lockKey <span class="token operator">=</span> LOCK_SHOP_KEY <span class="token operator">+</span> id<span class="token punctuation">;</span>        <span class="token keyword">boolean</span> isLock <span class="token operator">=</span> <span class="token function">tryLock</span><span class="token punctuation">(</span>lockKey<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 6.2.判断是否获取锁成功</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>isLock<span class="token punctuation">)</span><span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 6.3.成功，开启独立线程，实现缓存重建</span>            CACHE_REBUILD_EXECUTOR<span class="token punctuation">.</span><span class="token function">submit</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span>                <span class="token keyword">try</span> <span class="token punctuation">{</span>                    <span class="token comment" spellcheck="true">// 查询数据库</span>                    R newR <span class="token operator">=</span> dbFallback<span class="token punctuation">.</span><span class="token function">apply</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">;</span>                    <span class="token comment" spellcheck="true">// 重建缓存</span>                    <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">setWithLogicalExpire</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> newR<span class="token punctuation">,</span> time<span class="token punctuation">,</span> unit<span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Exception</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span>                    <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">RuntimeException</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token punctuation">}</span><span class="token keyword">finally</span> <span class="token punctuation">{</span>                    <span class="token comment" spellcheck="true">// 释放锁</span>                    <span class="token function">unlock</span><span class="token punctuation">(</span>lockKey<span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token punctuation">}</span>            <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 6.4.返回过期的商铺信息</span>        <span class="token keyword">return</span> r<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>方法5：根据指定的key查询缓存，并反序列化为指定类型，需要利用互斥锁解决缓存击穿问题</p><pre class="line-numbers language-java"><code class="language-java"> <span class="token keyword">public</span> <span class="token operator">&lt;</span>R<span class="token punctuation">,</span> ID<span class="token operator">></span> R <span class="token function">queryWithMutex</span><span class="token punctuation">(</span>String keyPrefix<span class="token punctuation">,</span> ID id<span class="token punctuation">,</span> Class<span class="token operator">&lt;</span>R<span class="token operator">></span> type<span class="token punctuation">,</span> Function<span class="token operator">&lt;</span>ID<span class="token punctuation">,</span> R<span class="token operator">></span> dbFallback<span class="token punctuation">,</span> Long time<span class="token punctuation">,</span> TimeUnit unit<span class="token punctuation">)</span> <span class="token punctuation">{</span>        String key <span class="token operator">=</span> keyPrefix <span class="token operator">+</span> id<span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 1.从redis查询商铺缓存</span>        String shopJson <span class="token operator">=</span> stringRedisTemplate<span class="token punctuation">.</span><span class="token function">opsForValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>key<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 2.判断是否存在</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>StrUtil<span class="token punctuation">.</span><span class="token function">isNotBlank</span><span class="token punctuation">(</span>shopJson<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 3.存在，直接返回</span>            <span class="token keyword">return</span> JSONUtil<span class="token punctuation">.</span><span class="token function">toBean</span><span class="token punctuation">(</span>shopJson<span class="token punctuation">,</span> type<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 判断命中的是否是空值</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>shopJson <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 返回一个错误信息</span>            <span class="token keyword">return</span> null<span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 4.实现缓存重建</span>        <span class="token comment" spellcheck="true">// 4.1.获取互斥锁</span>        String lockKey <span class="token operator">=</span> LOCK_SHOP_KEY <span class="token operator">+</span> id<span class="token punctuation">;</span>        R r <span class="token operator">=</span> null<span class="token punctuation">;</span>        <span class="token keyword">try</span> <span class="token punctuation">{</span>            <span class="token keyword">boolean</span> isLock <span class="token operator">=</span> <span class="token function">tryLock</span><span class="token punctuation">(</span>lockKey<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">// 4.2.判断是否获取成功</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>isLock<span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token comment" spellcheck="true">// 4.3.获取锁失败，休眠并重试</span>                Thread<span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">50</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token keyword">return</span> <span class="token function">queryWithMutex</span><span class="token punctuation">(</span>keyPrefix<span class="token punctuation">,</span> id<span class="token punctuation">,</span> type<span class="token punctuation">,</span> dbFallback<span class="token punctuation">,</span> time<span class="token punctuation">,</span> unit<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>            <span class="token comment" spellcheck="true">// 4.4.获取锁成功，根据id查询数据库</span>            r <span class="token operator">=</span> dbFallback<span class="token punctuation">.</span><span class="token function">apply</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">// 5.不存在，返回错误</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span>r <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token comment" spellcheck="true">// 将空值写入redis</span>                stringRedisTemplate<span class="token punctuation">.</span><span class="token function">opsForValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> <span class="token string">""</span><span class="token punctuation">,</span> CACHE_NULL_TTL<span class="token punctuation">,</span> TimeUnit<span class="token punctuation">.</span>MINUTES<span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token comment" spellcheck="true">// 返回错误信息</span>                <span class="token keyword">return</span> null<span class="token punctuation">;</span>            <span class="token punctuation">}</span>            <span class="token comment" spellcheck="true">// 6.存在，写入redis</span>            <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> r<span class="token punctuation">,</span> time<span class="token punctuation">,</span> unit<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">InterruptedException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">RuntimeException</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span><span class="token keyword">finally</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 7.释放锁</span>            <span class="token function">unlock</span><span class="token punctuation">(</span>lockKey<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 8.返回</span>        <span class="token keyword">return</span> r<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>在Service中调用方法</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Resource</span><span class="token keyword">private</span> CacheClient cacheClient<span class="token punctuation">;</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> Result <span class="token function">queryById</span><span class="token punctuation">(</span>Long id<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 解决缓存穿透</span>        Shop shop <span class="token operator">=</span> cacheClient                <span class="token punctuation">.</span><span class="token function">queryWithPassThrough</span><span class="token punctuation">(</span>CACHE_SHOP_KEY<span class="token punctuation">,</span> id<span class="token punctuation">,</span> Shop<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token operator">:</span><span class="token operator">:</span>getById<span class="token punctuation">,</span> CACHE_SHOP_TTL<span class="token punctuation">,</span> TimeUnit<span class="token punctuation">.</span>MINUTES<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 互斥锁解决缓存击穿</span>        <span class="token comment" spellcheck="true">// Shop shop = cacheClient</span>        <span class="token comment" spellcheck="true">//         .queryWithMutex(CACHE_SHOP_KEY, id, Shop.class, this::getById, CACHE_SHOP_TTL, TimeUnit.MINUTES);</span>        <span class="token comment" spellcheck="true">// 逻辑过期解决缓存击穿</span>        <span class="token comment" spellcheck="true">// Shop shop = cacheClient</span>        <span class="token comment" spellcheck="true">//         .queryWithLogicalExpire(CACHE_SHOP_KEY, id, Shop.class, this::getById, 20L, TimeUnit.SECONDS);</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>shop <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">fail</span><span class="token punctuation">(</span><span class="token string">"店铺不存在！"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 7.返回</span>        <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">ok</span><span class="token punctuation">(</span>shop<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h1 id="③优惠券秒杀"><a href="#③优惠券秒杀" class="headerlink" title="③优惠券秒杀"></a>③优惠券秒杀</h1><h2 id="❶Redis实现全局唯一ID"><a href="#❶Redis实现全局唯一ID" class="headerlink" title="❶Redis实现全局唯一ID"></a>❶Redis实现全局唯一ID</h2><blockquote><p>每个店铺都可以发布优惠券，当用户抢购时，就会生成订单并保存到tb_voucher_order这张表中，而订单表如果使用数据库自增ID就存在一些问题：</p><ul><li>id的规律性太明显</li><li>受单表数据量的限制</li></ul><p>场景分析一：如果我们的id具有太明显的规则，用户或者说商业对手很容易猜测出来我们的一些敏感信息，比如商城在一天时间内，卖出了多少单，这明显不合适。</p><p>场景分析二：随着我们商城规模越来越大，mysql的单表的容量不宜超过500W，数据量过大之后，我们要进行拆库拆表，但拆分表了之后，他们从逻辑上讲他们是同一张表，所以他们的id是不能一样的， 于是乎我们需要保证id的唯一性。</p></blockquote><p><strong>全局ID生成器</strong>，是一种在分布式系统下用来生成全局唯一ID的工具，一般要满足下列特性：</p><p><img src="https://img.jwt1399.top/img/202211251852632.png"></p><p><strong>全局唯一ID生成策略：UUID、Redis自增、snowflake算法、数据库自增</strong></p><p>为了增加ID的安全性，我们可以不直接使用Redis自增的数值，而是拼接一些其它信息：</p><p>Redis自增ID策略：ID构造是 时间戳 + 计数器，每天一个key，方便统计订单量</p><p><img src="https://img.jwt1399.top/img/202211251852180.png"></p><p>ID的组成部分：</p><ul><li><p>符号位：1bit，永远为0</p></li><li><p>时间戳：31bit，以秒为单位</p></li><li><p>序列号：32bit，秒内的计数器，支持每秒产生2^32个不同ID</p><ul><li>表示在某一秒下，这个自增域最大可以分配的bit个数，在当前这种配置下，每一秒可以分配2^32个数据</li></ul></li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Component</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">RedisIdWorker</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">/**     * 开始时间戳（2022-01-01）     */</span>    <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">long</span> BEGIN_TIMESTAMP <span class="token operator">=</span> 1640995200L<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">/**     * 序列号的位数     */</span>    <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> COUNT_BITS <span class="token operator">=</span> <span class="token number">32</span><span class="token punctuation">;</span>    <span class="token keyword">private</span> StringRedisTemplate stringRedisTemplate<span class="token punctuation">;</span>    <span class="token keyword">public</span> <span class="token function">RedisIdWorker</span><span class="token punctuation">(</span>StringRedisTemplate stringRedisTemplate<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">this</span><span class="token punctuation">.</span>stringRedisTemplate <span class="token operator">=</span> stringRedisTemplate<span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">public</span> <span class="token keyword">long</span> <span class="token function">nextId</span><span class="token punctuation">(</span>String keyPrefix<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 1.生成时间戳</span>        LocalDateTime now <span class="token operator">=</span> LocalDateTime<span class="token punctuation">.</span><span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">long</span> nowSecond <span class="token operator">=</span> now<span class="token punctuation">.</span><span class="token function">toEpochSecond</span><span class="token punctuation">(</span>ZoneOffset<span class="token punctuation">.</span>UTC<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">long</span> timestamp <span class="token operator">=</span> nowSecond <span class="token operator">-</span> BEGIN_TIMESTAMP<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//现在时间-开始时间戳</span>        <span class="token comment" spellcheck="true">// 2.生成序列号</span>        <span class="token comment" spellcheck="true">// 2.1.获取当前日期，精确到天</span>        String date <span class="token operator">=</span> now<span class="token punctuation">.</span><span class="token function">format</span><span class="token punctuation">(</span>DateTimeFormatter<span class="token punctuation">.</span><span class="token function">ofPattern</span><span class="token punctuation">(</span><span class="token string">"yyyy:MM:dd"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 2.2.自增长</span>        <span class="token keyword">long</span> count <span class="token operator">=</span> stringRedisTemplate<span class="token punctuation">.</span><span class="token function">opsForValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">increment</span><span class="token punctuation">(</span><span class="token string">"icr:"</span> <span class="token operator">+</span> keyPrefix <span class="token operator">+</span> <span class="token string">":"</span> <span class="token operator">+</span> date<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 3.拼接并返回</span>        <span class="token keyword">return</span> timestamp <span class="token operator">&lt;&lt;</span> COUNT_BITS <span class="token operator">|</span> count<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>测试类</strong></p><pre class="line-numbers language-java"><code class="language-java">        <span class="token annotation punctuation">@Resource</span>    <span class="token keyword">private</span> RedisIdWorker redisIdWorker<span class="token punctuation">;</span>    <span class="token keyword">private</span> ExecutorService es <span class="token operator">=</span> Executors<span class="token punctuation">.</span><span class="token function">newFixedThreadPool</span><span class="token punctuation">(</span><span class="token number">500</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token annotation punctuation">@Test</span>    <span class="token keyword">void</span> <span class="token function">testIdWorker</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">throws</span> InterruptedException <span class="token punctuation">{</span>        CountDownLatch latch <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">CountDownLatch</span><span class="token punctuation">(</span><span class="token number">300</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        Runnable task <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span>            <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> <span class="token number">100</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token keyword">long</span> id <span class="token operator">=</span> redisIdWorker<span class="token punctuation">.</span><span class="token function">nextId</span><span class="token punctuation">(</span><span class="token string">"order"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"id = "</span> <span class="token operator">+</span> id<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>            latch<span class="token punctuation">.</span><span class="token function">countDown</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span><span class="token punctuation">;</span>        <span class="token keyword">long</span> begin <span class="token operator">=</span> System<span class="token punctuation">.</span><span class="token function">currentTimeMillis</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> <span class="token number">300</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            es<span class="token punctuation">.</span><span class="token function">submit</span><span class="token punctuation">(</span>task<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        latch<span class="token punctuation">.</span><span class="token function">await</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">long</span> end <span class="token operator">=</span> System<span class="token punctuation">.</span><span class="token function">currentTimeMillis</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"time = "</span> <span class="token operator">+</span> <span class="token punctuation">(</span>end <span class="token operator">-</span> begin<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><blockquote><p>知识小贴士：countdownlatch</p><p>countdownlatch名为信号枪：主要的作用是同步协调在多线程的等待于唤醒问题</p><p>如果没有CountDownLatch ，那么由于程序是异步的，当异步程序没有执行完时，主线程就已经执行完了，然后我们期望的是分线程全部走完之后，主线程再走，所以我们此时需要使用到CountDownLatch，CountDownLatch 中有两个最重要的方法</p><ul><li><p>1、countDown</p></li><li><p>2、await</p></li></ul><p>await 方法是阻塞方法，我们担心分线程没有执行完时，main线程就先执行，所以使用await可以让main线程阻塞，那么什么时候main线程不再阻塞呢？当CountDownLatch 内部维护的变量变为0时，就不再阻塞，直接放行，那么什么时候CountDownLatch   维护的变量变为0 呢，我们只需要调用一次countDown ，内部变量就减少1，我们让分线程和变量绑定， 执行完一个分线程就减少一个变量，当分线程全部走完，CountDownLatch 维护的变量就是0，此时await就不再阻塞，统计出来的时间也就是所有分线程执行完后的时间。</p></blockquote><h2 id="❷添加优惠卷"><a href="#❷添加优惠卷" class="headerlink" title="❷添加优惠卷"></a>❷添加优惠卷</h2><p>每个店铺都可以发布优惠券，分为平价券和特价券。平价券可以任意购买，而特价券需要秒杀抢购：</p><p><img src="/../images/Redis-%E5%AE%9E%E6%88%98%E7%AF%87/1653365145124.png"></p><p>表信息：</p><ul><li>tb_voucher：优惠券的基本信息，优惠金额、使用规则等</li><li>tb_seckill_voucher：优惠券的库存、开始抢购时间，结束抢购时间。特价优惠券才需要填写这些信息</li></ul><p>平价卷由于优惠力度并不是很大，所以是可以任意领取；而特价券由于优惠力度大，就得限制数量，从表结构上也能看出，特价卷除了具有优惠卷的基本信息以外，还具有库存，抢购时间，结束时间等等字段</p><p>**新增普通卷代码：  **VoucherController</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@PostMapping</span><span class="token keyword">public</span> Result <span class="token function">addVoucher</span><span class="token punctuation">(</span><span class="token annotation punctuation">@RequestBody</span> Voucher voucher<span class="token punctuation">)</span> <span class="token punctuation">{</span>    voucherService<span class="token punctuation">.</span><span class="token function">save</span><span class="token punctuation">(</span>voucher<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">ok</span><span class="token punctuation">(</span>voucher<span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>新增秒杀卷代码：</strong></p><ul><li><strong>VoucherController</strong></li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@PostMapping</span><span class="token punctuation">(</span><span class="token string">"seckill"</span><span class="token punctuation">)</span><span class="token keyword">public</span> Result <span class="token function">addSeckillVoucher</span><span class="token punctuation">(</span><span class="token annotation punctuation">@RequestBody</span> Voucher voucher<span class="token punctuation">)</span> <span class="token punctuation">{</span>    voucherService<span class="token punctuation">.</span><span class="token function">addSeckillVoucher</span><span class="token punctuation">(</span>voucher<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">ok</span><span class="token punctuation">(</span>voucher<span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li><strong>VoucherServiceImpl</strong></li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Override</span><span class="token annotation punctuation">@Transactional</span><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">addSeckillVoucher</span><span class="token punctuation">(</span>Voucher voucher<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 保存优惠券</span>    <span class="token function">save</span><span class="token punctuation">(</span>voucher<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 保存秒杀信息</span>    SeckillVoucher seckillVoucher <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">SeckillVoucher</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    seckillVoucher<span class="token punctuation">.</span><span class="token function">setVoucherId</span><span class="token punctuation">(</span>voucher<span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    seckillVoucher<span class="token punctuation">.</span><span class="token function">setStock</span><span class="token punctuation">(</span>voucher<span class="token punctuation">.</span><span class="token function">getStock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    seckillVoucher<span class="token punctuation">.</span><span class="token function">setBeginTime</span><span class="token punctuation">(</span>voucher<span class="token punctuation">.</span><span class="token function">getBeginTime</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    seckillVoucher<span class="token punctuation">.</span><span class="token function">setEndTime</span><span class="token punctuation">(</span>voucher<span class="token punctuation">.</span><span class="token function">getEndTime</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    seckillVoucherService<span class="token punctuation">.</span><span class="token function">save</span><span class="token punctuation">(</span>seckillVoucher<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>添加一张特价券</strong></p><p><img src="https://img.jwt1399.top/img/202211252001419.png"></p><pre class="line-numbers language-json"><code class="language-json"><span class="token punctuation">{</span>    <span class="token property">"shopId"</span><span class="token operator">:</span> <span class="token number">1</span><span class="token punctuation">,</span>    <span class="token property">"title"</span><span class="token operator">:</span> <span class="token string">"100元特价劵"</span><span class="token punctuation">,</span>    <span class="token property">"subTitle"</span><span class="token operator">:</span> <span class="token string">"周一至周五均可使用"</span><span class="token punctuation">,</span>    <span class="token property">"rules"</span><span class="token operator">:</span> <span class="token string">"全场通用\\n无需预约\\n可无限叠加\\不兑现、不找零\n仅限堂食"</span><span class="token punctuation">,</span>    <span class="token property">"payValue"</span><span class="token operator">:</span> <span class="token number">8000</span><span class="token punctuation">,</span>    <span class="token property">"actualValue"</span><span class="token operator">:</span> <span class="token number">10000</span><span class="token punctuation">,</span>    <span class="token property">"type"</span><span class="token operator">:</span> <span class="token number">1</span><span class="token punctuation">,</span>    <span class="token property">"stock"</span><span class="token operator">:</span> <span class="token number">100</span><span class="token punctuation">,</span>    <span class="token property">"beginTime"</span><span class="token operator">:</span> <span class="token string">"2022-11-25T18:10:39"</span><span class="token punctuation">,</span>    <span class="token property">"endTime"</span><span class="token operator">:</span> <span class="token string">"2022-11-25T24:32:19"</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="❸实现秒杀下单"><a href="#❸实现秒杀下单" class="headerlink" title="❸实现秒杀下单"></a>❸实现秒杀下单</h2><p>下单核心思路：当我们点击抢购时，会触发右侧的请求，我们只需要编写对应的controller即可</p><p><img src="https://img.jwt1399.top/img/202211252011573.png"></p><p>秒杀下单应该思考的内容：</p><ul><li>秒杀是否开始或结束，如果尚未开始或已经结束则无法下单</li><li>库存是否充足，不足则无法下单</li></ul><p>下单核心逻辑分析：</p><p>当用户开始进行下单，我们应当去查询优惠卷信息，查询到优惠卷信息，判断是否满足秒杀条件，比如时间是否充足，如果时间充足，则进一步判断库存是否足够，如果两者都满足，则扣减库存，创建订单，然后返回订单id，如果有一个条件不满足则直接结束。</p><p><img src="https://img.jwt1399.top/img/202211252011684.png"></p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// VoucherOrderServiceImpl</span><span class="token annotation punctuation">@Override</span><span class="token keyword">public</span> Result <span class="token function">seckillVoucher</span><span class="token punctuation">(</span>Long voucherId<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 1.查询优惠券</span>    SeckillVoucher voucher <span class="token operator">=</span> seckillVoucherService<span class="token punctuation">.</span><span class="token function">getById</span><span class="token punctuation">(</span>voucherId<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 2.判断秒杀是否开始</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>voucher<span class="token punctuation">.</span><span class="token function">getBeginTime</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">isAfter</span><span class="token punctuation">(</span>LocalDateTime<span class="token punctuation">.</span><span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 尚未开始</span>        <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">fail</span><span class="token punctuation">(</span><span class="token string">"秒杀尚未开始！"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">// 3.判断秒杀是否已经结束</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>voucher<span class="token punctuation">.</span><span class="token function">getEndTime</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">isBefore</span><span class="token punctuation">(</span>LocalDateTime<span class="token punctuation">.</span><span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 尚未开始</span>        <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">fail</span><span class="token punctuation">(</span><span class="token string">"秒杀已经结束！"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">// 4.判断库存是否充足</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>voucher<span class="token punctuation">.</span><span class="token function">getStock</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">&lt;</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 库存不足</span>        <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">fail</span><span class="token punctuation">(</span><span class="token string">"库存不足！"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">//5，扣减库存</span>    <span class="token keyword">boolean</span> success <span class="token operator">=</span> seckillVoucherService<span class="token punctuation">.</span><span class="token function">update</span><span class="token punctuation">(</span><span class="token punctuation">)</span>            <span class="token punctuation">.</span><span class="token function">setSql</span><span class="token punctuation">(</span><span class="token string">"stock= stock -1"</span><span class="token punctuation">)</span>            <span class="token punctuation">.</span><span class="token function">eq</span><span class="token punctuation">(</span><span class="token string">"voucher_id"</span><span class="token punctuation">,</span> voucherId<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">update</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>success<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">//扣减库存</span>        <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">fail</span><span class="token punctuation">(</span><span class="token string">"库存不足！"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">//6.创建订单</span>    VoucherOrder voucherOrder <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">VoucherOrder</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 6.1.订单id</span>    <span class="token keyword">long</span> orderId <span class="token operator">=</span> redisIdWorker<span class="token punctuation">.</span><span class="token function">nextId</span><span class="token punctuation">(</span><span class="token string">"order"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    voucherOrder<span class="token punctuation">.</span><span class="token function">setId</span><span class="token punctuation">(</span>orderId<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 6.2.用户id</span>    Long userId <span class="token operator">=</span> UserHolder<span class="token punctuation">.</span><span class="token function">getUser</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    voucherOrder<span class="token punctuation">.</span><span class="token function">setUserId</span><span class="token punctuation">(</span>userId<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 6.3.代金券id</span>    voucherOrder<span class="token punctuation">.</span><span class="token function">setVoucherId</span><span class="token punctuation">(</span>voucherId<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token function">save</span><span class="token punctuation">(</span>voucherOrder<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">ok</span><span class="token punctuation">(</span>orderId<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="❹库存超卖问题分析"><a href="#❹库存超卖问题分析" class="headerlink" title="❹库存超卖问题分析"></a>❹库存超卖问题分析</h2><p>有关超卖问题分析：原有代码中是这么写的</p><pre class="line-numbers language-java"><code class="language-java"> <span class="token keyword">if</span> <span class="token punctuation">(</span>voucher<span class="token punctuation">.</span><span class="token function">getStock</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">&lt;</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 库存不足</span>        <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">fail</span><span class="token punctuation">(</span><span class="token string">"库存不足！"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">//5，扣减库存</span>    <span class="token keyword">boolean</span> success <span class="token operator">=</span> seckillVoucherService<span class="token punctuation">.</span><span class="token function">update</span><span class="token punctuation">(</span><span class="token punctuation">)</span>            <span class="token punctuation">.</span><span class="token function">setSql</span><span class="token punctuation">(</span><span class="token string">"stock= stock -1"</span><span class="token punctuation">)</span>            <span class="token punctuation">.</span><span class="token function">eq</span><span class="token punctuation">(</span><span class="token string">"voucher_id"</span><span class="token punctuation">,</span> voucherId<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">update</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>success<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">//扣减库存</span>        <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">fail</span><span class="token punctuation">(</span><span class="token string">"库存不足！"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>假设线程1过来查询库存，判断出来库存大于1，正准备去扣减库存，但是还没有来得及去扣减，此时线程2过来，线程2也去查询库存，发现这个数量一定也大于1，那么这两个线程都会去扣减库存，最终多个线程相当于一起去扣减库存，此时就会出现库存的超卖问题。</p><hr><p>超卖问题是典型的多线程安全问题，针对这一问题的常见解决方案就是加锁，对于加锁，通常有两种解决方案：</p><p><img src="https://img.jwt1399.top/img/202211252100546.png"></p><p>本项目我们采用乐观锁来解决超卖问题：</p><blockquote><p>乐观锁：会有一个版本号，每次操作数据会对版本号+1，再提交回数据时，会去校验是否比之前的版本大1 ，如果大1 ，则进行操作成功，这套机制的核心逻辑在于，如果在操作过程中，版本号只比原来大1 ，那么就意味着操作过程中没有人对他进行过修改，他的操作就是安全的，如果不大1，则数据被修改过。</p><p>乐观锁的典型代表：就是CAS，利用CAS进行无锁化机制加锁，var5 是操作前读取的内存值，while中的var1+var2 是预估值，如果预估值 &#x3D;&#x3D; 内存值，则代表中间没有被人修改过，此时就将新值去替换 内存值</p><p>  其中do while 是为了在操作失败时，再次进行自旋操作，即把之前的逻辑再操作一次。</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">int</span> var5<span class="token punctuation">;</span><span class="token keyword">do</span> <span class="token punctuation">{</span>    var5 <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">getIntVolatile</span><span class="token punctuation">(</span>var1<span class="token punctuation">,</span> var2<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span> <span class="token keyword">while</span><span class="token punctuation">(</span><span class="token operator">!</span><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">compareAndSwapInt</span><span class="token punctuation">(</span>var1<span class="token punctuation">,</span> var2<span class="token punctuation">,</span> var5<span class="token punctuation">,</span> var5 <span class="token operator">+</span> var4<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">return</span> var5<span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre></blockquote><p>项目中的使用方式是没有像CAS一样带自旋的操作，也没有对版本号+1 ，而是直接将stock做为版本号</p><img src="https://img.jwt1399.top/img/202211252116596.png" style="zoom:50%;" /><h2 id="❺乐观锁解决超卖问题"><a href="#❺乐观锁解决超卖问题" class="headerlink" title="❺乐观锁解决超卖问题"></a>❺乐观锁解决超卖问题</h2><p><strong>方案一</strong></p><p>VoucherOrderServiceImpl 在扣减库存时，改为：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">boolean</span> success <span class="token operator">=</span> seckillVoucherService<span class="token punctuation">.</span><span class="token function">update</span><span class="token punctuation">(</span><span class="token punctuation">)</span>            <span class="token punctuation">.</span><span class="token function">setSql</span><span class="token punctuation">(</span><span class="token string">"stock= stock -1"</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">//set stock = stock -1</span>            <span class="token punctuation">.</span><span class="token function">eq</span><span class="token punctuation">(</span><span class="token string">"voucher_id"</span><span class="token punctuation">,</span> voucherId<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">eq</span><span class="token punctuation">(</span><span class="token string">"stock"</span><span class="token punctuation">,</span>voucher<span class="token punctuation">.</span><span class="token function">getStock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">update</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//where id = ？ and stock = ?</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>代码分析：当扣减库存时的库存和之前我查询到的库存是一样的，就意味着没有人在中间修改过库存，那么此时就是安全的，但是以上这种方式通过测试发现会有很多失败的情况，失败的原因在于：在使用乐观锁过程中假设100个线程同时都拿到了100的库存，然后大家一起去进行扣减，但是100个人中只有1个人能扣减成功，其他的人在扣减时，发现库存已经被修改过了，所以此时这些线程都会失败。</p><p><strong>方案二</strong></p><p>方案一要求修改前后都保持一致，但是这样我们分析过，成功的概率太低，所以我们的乐观锁需要变一下，改成stock 大于 0 即可进行修改</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">boolean</span> success <span class="token operator">=</span> seckillVoucherService<span class="token punctuation">.</span><span class="token function">update</span><span class="token punctuation">(</span><span class="token punctuation">)</span>            <span class="token punctuation">.</span><span class="token function">setSql</span><span class="token punctuation">(</span><span class="token string">"stock= stock -1"</span><span class="token punctuation">)</span>            <span class="token punctuation">.</span><span class="token function">eq</span><span class="token punctuation">(</span><span class="token string">"voucher_id"</span><span class="token punctuation">,</span> voucherId<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">gt</span><span class="token punctuation">(</span><span class="token string">"stock"</span><span class="token punctuation">,</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">update</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//where id = ? and stock > 0</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><h2 id="❻优惠券秒杀-一人一单"><a href="#❻优惠券秒杀-一人一单" class="headerlink" title="❻优惠券秒杀-一人一单"></a>❻优惠券秒杀-一人一单</h2><blockquote><p>需求：修改秒杀业务，要求同一个优惠券，一个用户只能下一单</p></blockquote><p><strong>现在的问题在于：</strong>优惠卷是为了引流，但是目前的情况是，一个人可以无限制的抢这个优惠卷，所以我们应当增加一层逻辑，让一个用户只能下一个单，而不是让一个用户下多个单</p><p><strong>具体操作逻辑：</strong>首先判断优惠卷秒杀是否开始，如果开始，则进一步判断库存是否足够，然后再根据优惠卷id和用户id查询是否已经下过这个订单，如果下过这个订单，则不再下单，否则进行下单</p><p><img src="https://img.jwt1399.top/img/202211261532789.png"></p><p><strong>初步代码：增加一人一单逻辑</strong></p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">//VoucherOrderServiceImpl  </span><span class="token annotation punctuation">@Override</span><span class="token keyword">public</span> Result <span class="token function">seckillVoucher</span><span class="token punctuation">(</span>Long voucherId<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 1.查询优惠券</span>    SeckillVoucher voucher <span class="token operator">=</span> seckillVoucherService<span class="token punctuation">.</span><span class="token function">getById</span><span class="token punctuation">(</span>voucherId<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 2.判断秒杀是否开始</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>voucher<span class="token punctuation">.</span><span class="token function">getBeginTime</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">isAfter</span><span class="token punctuation">(</span>LocalDateTime<span class="token punctuation">.</span><span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 尚未开始</span>        <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">fail</span><span class="token punctuation">(</span><span class="token string">"秒杀尚未开始！"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">// 3.判断秒杀是否已经结束</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>voucher<span class="token punctuation">.</span><span class="token function">getEndTime</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">isBefore</span><span class="token punctuation">(</span>LocalDateTime<span class="token punctuation">.</span><span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 尚未开始</span>        <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">fail</span><span class="token punctuation">(</span><span class="token string">"秒杀已经结束！"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">// 4.判断库存是否充足</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>voucher<span class="token punctuation">.</span><span class="token function">getStock</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">&lt;</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 库存不足</span>        <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">fail</span><span class="token punctuation">(</span><span class="token string">"库存不足！"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">// 5.一人一单逻辑</span>    <span class="token comment" spellcheck="true">// 5.1.用户id</span>    Long userId <span class="token operator">=</span> UserHolder<span class="token punctuation">.</span><span class="token function">getUser</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">int</span> count <span class="token operator">=</span> <span class="token function">query</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">eq</span><span class="token punctuation">(</span><span class="token string">"user_id"</span><span class="token punctuation">,</span> userId<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">eq</span><span class="token punctuation">(</span><span class="token string">"voucher_id"</span><span class="token punctuation">,</span> voucherId<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">count</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 5.2.判断是否存在</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>count <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 用户已经购买过了</span>        <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">fail</span><span class="token punctuation">(</span><span class="token string">"用户已经购买过一次！"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">//6，扣减库存</span>      <span class="token keyword">boolean</span> success <span class="token operator">=</span> seckillVoucherService<span class="token punctuation">.</span><span class="token function">update</span><span class="token punctuation">(</span><span class="token punctuation">)</span>      <span class="token punctuation">.</span><span class="token function">setSql</span><span class="token punctuation">(</span><span class="token string">"stock= stock -1"</span><span class="token punctuation">)</span>      <span class="token punctuation">.</span><span class="token function">eq</span><span class="token punctuation">(</span><span class="token string">"voucher_id"</span><span class="token punctuation">,</span> voucherId<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">gt</span><span class="token punctuation">(</span><span class="token string">"stock"</span><span class="token punctuation">,</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">update</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//where id = ? and stock > 0</span>      <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>success<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">fail</span><span class="token punctuation">(</span><span class="token string">"库存不足！"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">//7.创建订单</span>    VoucherOrder voucherOrder <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">VoucherOrder</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 7.1.订单id</span>    <span class="token keyword">long</span> orderId <span class="token operator">=</span> redisIdWorker<span class="token punctuation">.</span><span class="token function">nextId</span><span class="token punctuation">(</span><span class="token string">"order"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    voucherOrder<span class="token punctuation">.</span><span class="token function">setId</span><span class="token punctuation">(</span>orderId<span class="token punctuation">)</span><span class="token punctuation">;</span>    voucherOrder<span class="token punctuation">.</span><span class="token function">setUserId</span><span class="token punctuation">(</span>userId<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 7.3.代金券id</span>    voucherOrder<span class="token punctuation">.</span><span class="token function">setVoucherId</span><span class="token punctuation">(</span>voucherId<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token function">save</span><span class="token punctuation">(</span>voucherOrder<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">ok</span><span class="token punctuation">(</span>orderId<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>存在问题：</strong>现在的问题还是和之前一样，并发过来，查询数据库，都不存在订单，所以我们还是需要加锁，但是乐观锁比较适合更新数据，而现在是插入数据，所以我们需要使用悲观锁操作</p><p><strong>注意：</strong>在这里提到了非常多的问题，我们需要慢慢的来思考，首先将初始方案封装一个createVoucherOrder方法，同时为了确保他线程安全，在方法上添加了一把synchronized 锁</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Transactional</span><span class="token keyword">public</span> <span class="token keyword">synchronized</span> Result <span class="token function">createVoucherOrder</span><span class="token punctuation">(</span>Long voucherId<span class="token punctuation">)</span> <span class="token punctuation">{</span>                Long userId <span class="token operator">=</span> UserHolder<span class="token punctuation">.</span><span class="token function">getUser</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>          <span class="token comment" spellcheck="true">// 5.一人一单逻辑</span>         <span class="token comment" spellcheck="true">// 5.1.查询订单</span>        <span class="token keyword">int</span> count <span class="token operator">=</span> <span class="token function">query</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">eq</span><span class="token punctuation">(</span><span class="token string">"user_id"</span><span class="token punctuation">,</span> userId<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">eq</span><span class="token punctuation">(</span><span class="token string">"voucher_id"</span><span class="token punctuation">,</span> voucherId<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">count</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 5.2.判断是否存在</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>count <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 用户已经购买过了</span>            <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">fail</span><span class="token punctuation">(</span><span class="token string">"用户已经购买过一次！"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 6.扣减库存</span>        <span class="token keyword">boolean</span> success <span class="token operator">=</span> seckillVoucherService<span class="token punctuation">.</span><span class="token function">update</span><span class="token punctuation">(</span><span class="token punctuation">)</span>                <span class="token punctuation">.</span><span class="token function">setSql</span><span class="token punctuation">(</span><span class="token string">"stock = stock - 1"</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// set stock = stock - 1</span>                <span class="token punctuation">.</span><span class="token function">eq</span><span class="token punctuation">(</span><span class="token string">"voucher_id"</span><span class="token punctuation">,</span> voucherId<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">gt</span><span class="token punctuation">(</span><span class="token string">"stock"</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// where id = ? and stock > 0</span>                <span class="token punctuation">.</span><span class="token function">update</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>success<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 扣减失败</span>            <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">fail</span><span class="token punctuation">(</span><span class="token string">"库存不足！"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 7.创建订单</span>        VoucherOrder voucherOrder <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">VoucherOrder</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 7.1.订单id</span>        <span class="token keyword">long</span> orderId <span class="token operator">=</span> redisIdWorker<span class="token punctuation">.</span><span class="token function">nextId</span><span class="token punctuation">(</span><span class="token string">"order"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        voucherOrder<span class="token punctuation">.</span><span class="token function">setId</span><span class="token punctuation">(</span>orderId<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 7.2.用户id</span>        voucherOrder<span class="token punctuation">.</span><span class="token function">setUserId</span><span class="token punctuation">(</span>userId<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 7.3.代金券id</span>        voucherOrder<span class="token punctuation">.</span><span class="token function">setVoucherId</span><span class="token punctuation">(</span>voucherId<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token function">save</span><span class="token punctuation">(</span>voucherOrder<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 7.返回订单id</span>        <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">ok</span><span class="token punctuation">(</span>orderId<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>但是这样添加锁，锁的粒度太粗了，在使用锁过程中，控制<strong>锁粒度</strong> 是一个非常重要的事情，因为如果锁的粒度太大，会导致每个线程进来都会锁住，所以我们需要去控制锁的粒度。</p><p>代码需要修改为：<br>intern() 这个方法是从常量池中拿到数据，如果我们直接使用userId.toString() 拿到的对象实际上是不同的对象，new出来的对象，我们使用锁必须保证锁必须是同一把，所以我们需要使用intern()方法</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Transactional</span><span class="token keyword">public</span>  Result <span class="token function">createVoucherOrder</span><span class="token punctuation">(</span>Long voucherId<span class="token punctuation">)</span> <span class="token punctuation">{</span>    Long userId <span class="token operator">=</span> UserHolder<span class="token punctuation">.</span><span class="token function">getUser</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">synchronized</span><span class="token punctuation">(</span>userId<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">intern</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">{</span>         <span class="token comment" spellcheck="true">// 5.一人一单逻辑</span>         <span class="token comment" spellcheck="true">// 5.1.查询订单</span>        <span class="token keyword">int</span> count <span class="token operator">=</span> <span class="token function">query</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">eq</span><span class="token punctuation">(</span><span class="token string">"user_id"</span><span class="token punctuation">,</span> userId<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">eq</span><span class="token punctuation">(</span><span class="token string">"voucher_id"</span><span class="token punctuation">,</span> voucherId<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">count</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 5.2.判断是否存在</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>count <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 用户已经购买过了</span>            <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">fail</span><span class="token punctuation">(</span><span class="token string">"用户已经购买过一次！"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 6.扣减库存</span>        <span class="token keyword">boolean</span> success <span class="token operator">=</span> seckillVoucherService<span class="token punctuation">.</span><span class="token function">update</span><span class="token punctuation">(</span><span class="token punctuation">)</span>                <span class="token punctuation">.</span><span class="token function">setSql</span><span class="token punctuation">(</span><span class="token string">"stock = stock - 1"</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// set stock = stock - 1</span>                <span class="token punctuation">.</span><span class="token function">eq</span><span class="token punctuation">(</span><span class="token string">"voucher_id"</span><span class="token punctuation">,</span> voucherId<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">gt</span><span class="token punctuation">(</span><span class="token string">"stock"</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// where id = ? and stock > 0</span>                <span class="token punctuation">.</span><span class="token function">update</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>success<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 扣减失败</span>            <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">fail</span><span class="token punctuation">(</span><span class="token string">"库存不足！"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 7.创建订单</span>        VoucherOrder voucherOrder <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">VoucherOrder</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 7.1.订单id</span>        <span class="token keyword">long</span> orderId <span class="token operator">=</span> redisIdWorker<span class="token punctuation">.</span><span class="token function">nextId</span><span class="token punctuation">(</span><span class="token string">"order"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        voucherOrder<span class="token punctuation">.</span><span class="token function">setId</span><span class="token punctuation">(</span>orderId<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 7.2.用户id</span>        voucherOrder<span class="token punctuation">.</span><span class="token function">setUserId</span><span class="token punctuation">(</span>userId<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 7.3.代金券id</span>        voucherOrder<span class="token punctuation">.</span><span class="token function">setVoucherId</span><span class="token punctuation">(</span>voucherId<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token function">save</span><span class="token punctuation">(</span>voucherOrder<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 7.返回订单id</span>        <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">ok</span><span class="token punctuation">(</span>orderId<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>但是以上代码还是存在问题，问题的原因在于当前方法被spring的事务控制，如果你在方法内部加锁，可能会导致当前方法事务还没有提交，但是锁已经释放也会导致问题，所以我们选择将当前方法整体包裹起来，确保事务不会出现问题。在seckillVoucher 方法中，添加以下逻辑，这样就能保证事务的特性，同时也控制了锁的粒度</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Override</span><span class="token keyword">public</span> Result <span class="token function">seckillVoucher</span><span class="token punctuation">(</span>Long voucherId<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">//....</span>    Long userId <span class="token operator">=</span> UserHolder<span class="token punctuation">.</span><span class="token function">getUser</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">synchronized</span><span class="token punctuation">(</span>userId<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">intern</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">{</span>      <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">createVoucherOrder</span><span class="token punctuation">(</span>voucherId<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>但是以上做法依然有问题，因为你调用的方法，其实是<code>this.</code>的方式调用的，事务想要生效，还得利用代理来生效，所以这个地方，我们需要获得原始的事务对象， 来操作事务</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Override</span><span class="token keyword">public</span> Result <span class="token function">seckillVoucher</span><span class="token punctuation">(</span>Long voucherId<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">//....</span>  Long userId <span class="token operator">=</span> UserHolder<span class="token punctuation">.</span><span class="token function">getUser</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">synchronized</span> <span class="token punctuation">(</span>userId<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">intern</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    IVoucherOrderService proxy <span class="token operator">=</span> <span class="token punctuation">(</span>IVoucherOrderService<span class="token punctuation">)</span> AopContext<span class="token punctuation">.</span><span class="token function">currentProxy</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> proxy<span class="token punctuation">.</span><span class="token function">createVoucherOrder</span><span class="token punctuation">(</span>voucherId<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>需要添加依赖和注解</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.aspectj<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>aspectjweaver<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@EnableAspectJAutoProxy</span><span class="token punctuation">(</span>exposeProxy <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">//添加这一条</span><span class="token annotation punctuation">@MapperScan</span><span class="token punctuation">(</span><span class="token string">"com.kbdp.mapper"</span><span class="token punctuation">)</span><span class="token annotation punctuation">@SpringBootApplication</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">KbDianPingApplication</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>        SpringApplication<span class="token punctuation">.</span><span class="token function">run</span><span class="token punctuation">(</span>KbDianPingApplication<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">,</span> args<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="❼集群环境下的并发问题"><a href="#❼集群环境下的并发问题" class="headerlink" title="❼集群环境下的并发问题"></a>❼集群环境下的并发问题</h2><p>通过加锁可以解决在单机情况下的一人一单安全问题，但是在集群模式下就不行了。</p><p>1、我们将服务启动两份，端口分别为8081和8082：</p><p><img src="https://img.jwt1399.top/img/202211262006305.png"></p><p>2、然后修改nginx的conf目录下的nginx.conf文件，配置反向代理和负载均衡：</p><p><img src="https://img.jwt1399.top/img/202211262006090.png"></p><p><strong>有关锁失效原因分析</strong></p><p>由于现在我们部署了多个tomcat，每个tomcat都有一个属于自己的jvm，那么假设在服务器A的tomcat内部，有两个线程，这两个线程由于使用的是同一份代码，那么他们的锁对象是同一个，是可以实现互斥的，但是如果现在是服务器B的tomcat内部，又有两个线程，但是他们的锁对象写的虽然和服务器A一样，但是锁对象却不是同一个，所以线程3和线程4可以实现互斥，但是却无法和线程1和线程2实现互斥，这就是集群环境下，syn锁失效的原因，在这种情况下，我们就需要使用分布式锁来解决这个问题。</p><p><img src="https://img.jwt1399.top/img/202211262007757.png"></p><h1 id="④分布式锁"><a href="#④分布式锁" class="headerlink" title="④分布式锁"></a>④分布式锁</h1><h2 id="❶基本原理"><a href="#❶基本原理" class="headerlink" title="❶基本原理"></a>❶基本原理</h2><p>分布式锁：满足分布式系统或集群模式下多进程可见并且互斥的锁。</p><p>分布式锁的核心思想：让大家都使用同一把锁，只要大家使用的是同一把锁，那么我们就能锁住线程，不让线程进行，让程序串行执行，这就是分布式锁的核心思路</p><p><img src="https://img.jwt1399.top/img/202211262012190.png"></p><p>分布式锁应该满足什么样的条件呢？</p><p><img src="https://img.jwt1399.top/img/202211262011958.png"></p><ul><li><p>可见性：多个线程都能看到相同的结果</p></li><li><p>互斥：互斥是分布式锁的最基本的条件，使得程序串行执行</p></li><li><p>高可用：程序不易崩溃，时时刻刻都保证较高的可用性</p></li><li><p>高性能：由于加锁本身就让性能降低，所有对于分布式锁本身需要他就较高的加锁性能和释放锁性能</p></li><li><p>安全性：安全也是程序中必不可少的一环</p></li></ul><p>常见的分布式锁有三种</p><ul><li><p>Mysql：mysql本身就带有锁机制，但是由于mysql性能本身一般，所以分布式锁的情况下，使用mysql作为分布式锁比较少见</p></li><li><p>Redis：现在企业级开发中基本都使用redis或者zookeeper作为分布式锁，redis利用setnx方法，如果插入key成功，表示获得到了锁，如果有人插入成功，其他人插入失败表示无法获得到锁，利用这套逻辑来实现分布式锁</p></li><li><p>Zookeeper：zookeeper也是企业级开发中较好的一个实现分布式锁的方案</p></li></ul><p><img src="https://img.jwt1399.top/img/202211262015600.png"></p><h2 id="❷Redis分布式锁实现思路"><a href="#❷Redis分布式锁实现思路" class="headerlink" title="❷Redis分布式锁实现思路"></a>❷Redis分布式锁实现思路</h2><p>实现分布式锁时需要实现的两个基本方法：</p><ul><li><p>获取锁：</p><ul><li>互斥：确保只能有一个线程获取锁</li><li>非阻塞：尝试一次，成功返回true，失败返回false</li></ul></li></ul><pre class="line-numbers language-sql"><code class="language-sql"><span class="token comment" spellcheck="true"># 添加锁，NX是互斥 EX是设置超时时间</span><span class="token keyword">SET</span> <span class="token keyword">Lock</span> threadl EX <span class="token number">10</span> NX<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><pre class="line-numbers language-sql"><code class="language-sql"><span class="token comment" spellcheck="true"># 上面命令是下面两条命令的结合</span><span class="token comment" spellcheck="true"># 上面命令是下面两条命令的同时执行，为了保证原子性，防止应为redis宕机，导致键无法删除</span><span class="token comment" spellcheck="true"># 添加锁，利用setnx的互斥特性</span>SETNX <span class="token keyword">lock</span> thread1<span class="token comment" spellcheck="true"># 添加锁过期时间，避免服务宕机引起的死锁</span>EXPIRE <span class="token keyword">lock</span> <span class="token number">10</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>释放锁：<ul><li>手动释放</li><li>超时释放：获取锁时添加一个超时时间</li></ul></li></ul><pre class="line-numbers language-sql"><code class="language-sql"><span class="token comment" spellcheck="true"># 释放锁，删除即可</span>DEL <span class="token keyword">key</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>核心思路：利用 redis 的 SETNX 方法，当有多个线程进入时，我们就利用该方法，第一个线程进入时，redis 中就有这个key了，返回了1，如果结果是1，则表示他抢到了锁，那么他去执行业务，然后再删除锁，退出锁逻辑，没有抢到锁的等待一定时间后重试即可</p><img src="https://img.jwt1399.top/img/202211262049701.png"  style="zoom: 50%;" /><h2 id="❸实现分布式锁初级版本"><a href="#❸实现分布式锁初级版本" class="headerlink" title="❸实现分布式锁初级版本"></a>❸实现分布式锁初级版本</h2><blockquote><p>需求：定义一个类，实现下面接口，利用Redis实现分布式锁功能。</p></blockquote><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">ILock</span> <span class="token punctuation">{</span>  <span class="token comment" spellcheck="true">/**     * 尝试获取锁       * @param timeoutSec 锁持有的超时时间，过期后自动释放    * @return true代表获取锁成功; false代表获取锁失败      */</span>     <span class="token keyword">boolean</span> <span class="token function">tryLock</span><span class="token punctuation">(</span><span class="token keyword">long</span> timeoutSec<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment" spellcheck="true">/**   * 释放锁     */</span>    <span class="token keyword">void</span> <span class="token function">unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>实现类</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">SimpleRedisLock</span> <span class="token keyword">implements</span> <span class="token class-name">ILock</span><span class="token punctuation">{</span>      <span class="token keyword">private</span> String name<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//服务名称作为键</span>    <span class="token keyword">private</span> StringRedisTemplate stringRedisTemplate<span class="token punctuation">;</span>    <span class="token keyword">public</span> <span class="token function">SimpleRedisLock</span><span class="token punctuation">(</span>String name<span class="token punctuation">,</span> StringRedisTemplate stringRedisTemplate<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">this</span><span class="token punctuation">.</span>name <span class="token operator">=</span> name<span class="token punctuation">;</span>        <span class="token keyword">this</span><span class="token punctuation">.</span>stringRedisTemplate <span class="token operator">=</span> stringRedisTemplate<span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> <span class="token keyword">boolean</span> <span class="token function">tryLock</span><span class="token punctuation">(</span><span class="token keyword">long</span> timeoutSec<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>获取锁：利用setnx方法进行加锁，同时增加过期时间，防止死锁，可以保证加锁和增加过期时间具有原子性</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> String KEY_PREFIX<span class="token operator">=</span><span class="token string">"lock:"</span><span class="token punctuation">;</span><span class="token annotation punctuation">@Override</span><span class="token keyword">public</span> <span class="token keyword">boolean</span> <span class="token function">tryLock</span><span class="token punctuation">(</span><span class="token keyword">long</span> timeoutSec<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 获取线程标示作为值</span>    String threadId <span class="token operator">=</span> Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span>    <span class="token comment" spellcheck="true">// 获取锁</span>    Boolean success <span class="token operator">=</span> stringRedisTemplate<span class="token punctuation">.</span><span class="token function">opsForValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span>            <span class="token punctuation">.</span><span class="token function">setIfAbsent</span><span class="token punctuation">(</span>KEY_PREFIX <span class="token operator">+</span> name<span class="token punctuation">,</span> threadId <span class="token operator">+</span> <span class="token string">""</span><span class="token punctuation">,</span> timeoutSec<span class="token punctuation">,</span> TimeUnit<span class="token punctuation">.</span>SECONDS<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> Boolean<span class="token punctuation">.</span>TRUE<span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span>success<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//避免自动拆箱空指针</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>释放锁释：防止删除别人的锁</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">//通过del删除锁</span>    stringRedisTemplate<span class="token punctuation">.</span><span class="token function">delete</span><span class="token punctuation">(</span>KEY_PREFIX <span class="token operator">+</span> name<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><ul><li>修改业务代码</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Override</span><span class="token keyword">public</span> Result <span class="token function">seckillVoucher</span><span class="token punctuation">(</span>Long voucherId<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 1.查询优惠券</span>    SeckillVoucher voucher <span class="token operator">=</span> seckillVoucherService<span class="token punctuation">.</span><span class="token function">getById</span><span class="token punctuation">(</span>voucherId<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 2.判断秒杀是否开始</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>voucher<span class="token punctuation">.</span><span class="token function">getBeginTime</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">isAfter</span><span class="token punctuation">(</span>LocalDateTime<span class="token punctuation">.</span><span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>      <span class="token comment" spellcheck="true">// 尚未开始</span>      <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">fail</span><span class="token punctuation">(</span><span class="token string">"秒杀尚未开始！"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">// 3.判断秒杀是否已经结束</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>voucher<span class="token punctuation">.</span><span class="token function">getEndTime</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">isBefore</span><span class="token punctuation">(</span>LocalDateTime<span class="token punctuation">.</span><span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>      <span class="token comment" spellcheck="true">// 尚未开始</span>      <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">fail</span><span class="token punctuation">(</span><span class="token string">"秒杀已经结束！"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">// 4.判断库存是否充足</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>voucher<span class="token punctuation">.</span><span class="token function">getStock</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">&lt;</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>      <span class="token comment" spellcheck="true">// 库存不足</span>      <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">fail</span><span class="token punctuation">(</span><span class="token string">"库存不足！"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    Long userId <span class="token operator">=</span> UserHolder<span class="token punctuation">.</span><span class="token function">getUser</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">//创建锁对象(新增代码)</span>    SimpleRedisLock lock <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">SimpleRedisLock</span><span class="token punctuation">(</span><span class="token string">"order:"</span> <span class="token operator">+</span> userId<span class="token punctuation">,</span> stringRedisTemplate<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">//获取锁对象</span>    <span class="token keyword">boolean</span> isLock <span class="token operator">=</span> lock<span class="token punctuation">.</span><span class="token function">tryLock</span><span class="token punctuation">(</span><span class="token number">1200</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">//加锁失败</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>isLock<span class="token punctuation">)</span> <span class="token punctuation">{</span>      <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">fail</span><span class="token punctuation">(</span><span class="token string">"不允许重复下单"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">try</span> <span class="token punctuation">{</span>      <span class="token comment" spellcheck="true">//获取代理对象(事务)</span>      IVoucherOrderService proxy <span class="token operator">=</span> <span class="token punctuation">(</span>IVoucherOrderService<span class="token punctuation">)</span> AopContext<span class="token punctuation">.</span><span class="token function">currentProxy</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token keyword">return</span> proxy<span class="token punctuation">.</span><span class="token function">createVoucherOrder</span><span class="token punctuation">(</span>voucherId<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>      <span class="token comment" spellcheck="true">//释放锁</span>      lock<span class="token punctuation">.</span><span class="token function">unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="❹Redis分布式锁误删问题"><a href="#❹Redis分布式锁误删问题" class="headerlink" title="❹Redis分布式锁误删问题"></a>❹Redis分布式锁误删问题</h2><p><strong>问题描述</strong>：持有锁的线程在锁的内部出现了阻塞，导致他的锁超时自动释放，这时其他线程，线程2来尝试获得锁，就拿到了这把锁，然后线程2在持有锁执行过程中，线程1反应过来，继续执行，而线程1执行过程中，走到了删除锁逻辑，此时就会把本应该属于线程2的锁进行删除，这就是误删别人锁的情况说明</p><p><img src="https://img.jwt1399.top/img/202211262127536.png"></p><p><strong>解决方案</strong>：解决方案就是在每个线程释放锁的时候，去判断一下当前这把锁是否属于自己，如果属于自己，则不进行锁的删除，假设还是上边的情况，线程1卡顿，锁自动释放，线程2进入到锁的内部执行逻辑，此时线程1反应过来，然后删除锁，但是线程1，一看当前这把锁不是属于自己，于是不进行删除锁逻辑，当线程2走到删除锁逻辑时，如果没有卡过自动释放锁的时间点，则判断当前这把锁是属于自己的，于是删除这把锁。</p><p><img src="https://img.jwt1399.top/img/202211262127624.png"></p><h2 id="❺解决分布式锁误删问题"><a href="#❺解决分布式锁误删问题" class="headerlink" title="❺解决分布式锁误删问题"></a>❺解决分布式锁误删问题</h2><p>修改之前的分布式锁实现使其满足：在获取锁时存入线程标示（可以用UUID表示），在释放锁时先获取锁中的线程标示，判断是否与当前线程标示一致</p><ul><li>如果一致则释放锁</li><li>如果不一致则不释放锁</li></ul><p>核心逻辑：在存入锁时，放入自己线程的标识，在删除锁时，判断当前这把锁的标识是不是自己存入的，如果是，则进行删除，如果不是，则不进行删除。</p><img src="https://img.jwt1399.top/img/202211262127471.png" style="zoom:50%;" /><ul><li>获取锁</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> String ID_PREFIX <span class="token operator">=</span> UUID<span class="token punctuation">.</span><span class="token function">randomUUID</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">"-"</span><span class="token punctuation">;</span><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> String KEY_PREFIX<span class="token operator">=</span><span class="token string">"lock:"</span><span class="token punctuation">;</span><span class="token annotation punctuation">@Override</span><span class="token keyword">public</span> <span class="token keyword">boolean</span> <span class="token function">tryLock</span><span class="token punctuation">(</span><span class="token keyword">long</span> timeoutSec<span class="token punctuation">)</span> <span class="token punctuation">{</span>   <span class="token comment" spellcheck="true">// 获取线程标示</span>   String threadId <span class="token operator">=</span> ID_PREFIX <span class="token operator">+</span> Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>   <span class="token comment" spellcheck="true">// 获取锁</span>   Boolean success <span class="token operator">=</span> stringRedisTemplate<span class="token punctuation">.</span><span class="token function">opsForValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span>                <span class="token punctuation">.</span><span class="token function">setIfAbsent</span><span class="token punctuation">(</span>KEY_PREFIX <span class="token operator">+</span> name<span class="token punctuation">,</span> threadId<span class="token punctuation">,</span> timeoutSec<span class="token punctuation">,</span> TimeUnit<span class="token punctuation">.</span>SECONDS<span class="token punctuation">)</span><span class="token punctuation">;</span>   <span class="token keyword">return</span> Boolean<span class="token punctuation">.</span>TRUE<span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span>success<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>释放锁</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Override</span><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 获取线程标示</span>    String threadId <span class="token operator">=</span> ID_PREFIX <span class="token operator">+</span> Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 获取锁中的标示</span>    String id <span class="token operator">=</span> stringRedisTemplate<span class="token punctuation">.</span><span class="token function">opsForValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>KEY_PREFIX <span class="token operator">+</span> name<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 判断标示是否一致</span>    <span class="token keyword">if</span><span class="token punctuation">(</span>threadId<span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 释放锁</span>        stringRedisTemplate<span class="token punctuation">.</span><span class="token function">delete</span><span class="token punctuation">(</span>KEY_PREFIX <span class="token operator">+</span> name<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>测试：</strong>在我们修改完此处代码后，重启工程，然后启动两个线程，第一个线程持有锁后，手动释放锁，第二个线程 此时进入到锁内部，再放行第一个线程，此时第一个线程由于锁的value值并非是自己，所以不能释放锁，也就无法删除别人的锁，此时第二个线程能够正确释放锁，通过这个案例初步说明我们解决了锁误删的问题。</p><h2 id="❻分布式锁的原子性问题"><a href="#❻分布式锁的原子性问题" class="headerlink" title="❻分布式锁的原子性问题"></a>❻分布式锁的原子性问题</h2><p>更为极端的误删问题：线程1现在持有锁之后，在执行业务逻辑过程中，他正准备删除锁，而且已经走到了条件判断的过程中，比如当前这把锁确实是属于他自己的，正准备删除锁，但是突然阻塞了，而且此时他的锁也到期了，那么此时线程2进来，当线程1卡顿结束后，他直接就会执行删除锁那行代码，相当于条件判断并没有起到作用，就会直接删除线程2的锁，这就是删锁时的原子性问题，之所以有这个问题，是<strong>因为线程1的拿锁，比锁，删锁，实际上并不是原子性的</strong>，我们要防止这种情况发生</p><p><img src="https://img.jwt1399.top/img/202211262150817.png"></p><h2 id="❼Lua解决多条命令原子性问题"><a href="#❼Lua解决多条命令原子性问题" class="headerlink" title="❼Lua解决多条命令原子性问题"></a>❼Lua解决多条命令原子性问题</h2><blockquote><p>Redis提供了Lua脚本功能，在一个脚本中编写多条Redis命令，确保多条命令执行时的原子性。</p><p>Lua是一种编程语言，基本语法可以参考网站：<a href="https://www.runoob.com/lua/lua-tutorial.html">https://www.runoob.com/lua/lua-tutorial.html</a></p><p>这里重点介绍Redis提供的调用函数，我们可以使用lua去操作redis，又能保证他的原子性，这样就可以实现拿锁、比锁、删锁是一个原子性动作了。</p></blockquote><p>这里重点介绍Redis提供的调用函数，语法如下：</p><pre class="line-numbers language-lua"><code class="language-lua">redis<span class="token punctuation">.</span><span class="token function">call</span><span class="token punctuation">(</span><span class="token string">'命令名称'</span><span class="token punctuation">,</span> <span class="token string">'key'</span><span class="token punctuation">,</span> <span class="token string">'其它参数'</span><span class="token punctuation">,</span> <span class="token punctuation">...</span><span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>例如，我们要执行set name jack，则脚本是这样：</p><pre class="line-numbers language-lua"><code class="language-lua"><span class="token operator">#</span> 执行 set name jackredis<span class="token punctuation">.</span><span class="token function">call</span><span class="token punctuation">(</span><span class="token string">'set'</span><span class="token punctuation">,</span> <span class="token string">'name'</span><span class="token punctuation">,</span> <span class="token string">'jack'</span><span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>例如，我们要先执行set name Rose，再执行get name，则脚本如下：</p><pre class="line-numbers language-lua"><code class="language-lua"><span class="token operator">#</span> 先执行 set name jackredis<span class="token punctuation">.</span><span class="token function">call</span><span class="token punctuation">(</span><span class="token string">'set'</span><span class="token punctuation">,</span> <span class="token string">'name'</span><span class="token punctuation">,</span> <span class="token string">'Rose'</span><span class="token punctuation">)</span><span class="token operator">#</span> 再执行 get name<span class="token keyword">local</span> name <span class="token operator">=</span> redis<span class="token punctuation">.</span><span class="token function">call</span><span class="token punctuation">(</span><span class="token string">'get'</span><span class="token punctuation">,</span> <span class="token string">'name'</span><span class="token punctuation">)</span><span class="token operator">#</span> 返回<span class="token keyword">return</span> name<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>写好脚本以后，需要用Redis命令来调用脚本，调用脚本的常见命令如下：</p><p><img src="https://img.jwt1399.top/img/202211271513040.png"></p><p>例如，我们要执行 redis.call(‘set’, ‘name’, ‘jack’) 这个脚本，语法如下：</p><p><img src="https://img.jwt1399.top/img/202211271458507.png"></p><p>如果脚本中的key、value不想写死，可以作为参数传递。key类型参数会放入KEYS数组，其它参数会放入ARGV数组，在脚本中可以从KEYS和ARGV数组获取这些参数：</p><p><img src="https://img.jwt1399.top/img/202211271458421.png"></p><p>释放锁的业务流程是这样的</p><p>​1、获取锁中的线程标示</p><p>​2、判断是否与指定的标示（当前线程标示）一致</p><p>​3、如果一致则释放锁（删除）</p><p>​4、如果不一致则什么都不做</p><p>最终我们操作redis的拿锁比锁删锁的lua脚本就会变成这样</p><pre class="line-numbers language-lua"><code class="language-lua"><span class="token comment" spellcheck="true">-- 这里的 KEYS[1] 就是锁的key，这里的ARGV[1] 就是当前线程标示</span><span class="token comment" spellcheck="true">-- 获取锁中的标示，判断是否与当前线程标示一致</span><span class="token keyword">if</span> <span class="token punctuation">(</span>redis<span class="token punctuation">.</span><span class="token function">call</span><span class="token punctuation">(</span><span class="token string">'GET'</span><span class="token punctuation">,</span> KEYS<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token operator">==</span> ARGV<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token keyword">then</span>  <span class="token comment" spellcheck="true">-- 一致，则删除锁</span>  <span class="token keyword">return</span> redis<span class="token punctuation">.</span><span class="token function">call</span><span class="token punctuation">(</span><span class="token string">'DEL'</span><span class="token punctuation">,</span> KEYS<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token keyword">end</span><span class="token comment" spellcheck="true">-- 不一致，则直接返回</span><span class="token keyword">return</span> <span class="token number">0</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="❽利用Java调用Lua改造分布式锁"><a href="#❽利用Java调用Lua改造分布式锁" class="headerlink" title="❽利用Java调用Lua改造分布式锁"></a>❽利用Java调用Lua改造分布式锁</h2><p>RedisTemplate调用Lua脚本的API如下：利用execute(lua脚本, key, 参数)方法去执行lua脚本</p><p><img src="https://img.jwt1399.top/img/202211271515973.png"></p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// 加载lua脚本</span><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> DefaultRedisScript<span class="token operator">&lt;</span>Long<span class="token operator">></span> UNLOCK_SCRIPT<span class="token punctuation">;</span> <span class="token keyword">static</span> <span class="token punctuation">{</span>  UNLOCK_SCRIPT <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">DefaultRedisScript</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  UNLOCK_SCRIPT<span class="token punctuation">.</span><span class="token function">setLocation</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">ClassPathResource</span><span class="token punctuation">(</span><span class="token string">"unlock.lua"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  UNLOCK_SCRIPT<span class="token punctuation">.</span><span class="token function">setResultType</span><span class="token punctuation">(</span>Long<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">// 释放锁</span><span class="token annotation punctuation">@Override</span><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 调用lua脚本</span>    stringRedisTemplate<span class="token punctuation">.</span><span class="token function">execute</span><span class="token punctuation">(</span>            UNLOCK_SCRIPT<span class="token punctuation">,</span>            Collections<span class="token punctuation">.</span><span class="token function">singletonList</span><span class="token punctuation">(</span>KEY_PREFIX <span class="token operator">+</span> name<span class="token punctuation">)</span><span class="token punctuation">,</span>            ID_PREFIX <span class="token operator">+</span> Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">//经过以上代码改造后，我们就能够实现 拿锁比锁删锁的原子性动作了~</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>测试逻辑：</strong></p><p>第一个线程进来，得到了锁，手动删除锁，模拟锁超时了，其他线程会执行lua来抢锁，当第一天线程利用lua删除锁时，lua能保证他不能删除他的锁，第二个线程删除锁时，利用lua同样可以保证不会删除别人的锁，同时还能保证原子性。</p><h2 id="❾总结"><a href="#❾总结" class="headerlink" title="❾总结"></a>❾总结</h2><p>基于Redis的分布式锁实现思路：</p><ul><li>利用set nx ex获取锁，并设置过期时间，保存线程标示</li><li>释放锁时先判断线程标示是否与自己一致，一致则删除锁<ul><li>利用set nx满足互斥性</li><li>利用set ex保证故障时锁依然能释放，避免死锁，提高安全性</li><li>利用Redis集群保证高可用和高并发特性</li></ul></li></ul><p>笔者总结：</p><ul><li><p>1、利用添加过期时间，防止死锁问题的发生;</p></li><li><p>2、有了过期时间之后，可能出现误删别人锁的问题；</p><ul><li>删之前通过拿锁，比锁，删锁这个逻辑来解决，即删之前判断一下当前这把锁是否是属于自己的</li></ul></li><li><p>3、但是还有原子性问题，即我们没法保证拿锁比锁删锁是一个原子性的动作；</p><ul><li>通过lua表达式来解决这个问题</li></ul></li></ul><p>但是目前还剩下一个问题：锁不住，什么是锁不住呢，如果当过期时间到了之后，我们可以给他续期一下，比如续个30s，就好像是网吧上网， 网费到了之后，然后说，网管，再给我来10块的，是不是后边的问题都不会发生了，那么续期问题怎么解决呢，可以依赖于我们接下来要学习redission啦</p><h1 id="⑤分布式锁-redisson"><a href="#⑤分布式锁-redisson" class="headerlink" title="⑤分布式锁-redisson"></a>⑤分布式锁-redisson</h1><h2 id="❶redisson功能介绍"><a href="#❶redisson功能介绍" class="headerlink" title="❶redisson功能介绍"></a>❶redisson功能介绍</h2><p>基于setnx实现的分布式锁存在下面的问题：</p><ul><li><p><strong>不可重入问题</strong>：指获得锁的线程不可以再次进入到相同的锁的代码块中，可重入锁的意义在于防止死锁，比如HashTable这样的代码中，他的方法都是使用synchronized修饰的，假如他在一个方法内，调用另一个方法，那么此时如果是不可重入的，不就死锁了吗？所以可重入锁的主要意义是防止死锁，我们的synchronized和Lock锁都是可重入的。</p></li><li><p><strong>不可重试</strong>：指获取锁只尝试一次就返回false，没有重试机制。我们认为合理的情况是：当线程在获得锁失败后，他应该能再次尝试获得锁。</p></li><li><p><strong>超时释放：</strong>我们在加锁时增加了过期时间，这样的我们可以防止死锁，但是如果卡顿的时间超长，虽然我们采用了lua表达式防止删锁的时候，误删别人的锁，但是毕竟没有锁住，有安全隐患</p></li><li><p><strong>主从一致性：</strong> 如果Redis提供了主从集群，当我们向集群写数据时，主机需要异步的将数据同步给从机，而万一在同步过去之前，主机宕机了，就会出现死锁问题。</p></li></ul><blockquote><p>那么什么是Redisson呢</p></blockquote><p>Redisson是一个在Redis的基础上实现的Java驻内存数据网格（In-Memory Data Grid）。即是一个在Redis的基础上实现的分布式工具集合。它不仅提供了一系列的分布式的Java常用对象，还提供了许多分布式服务，其中就包含了各种分布式锁的实现。Redission提供了分布式锁的多种多样的功能</p><p>8.分布式锁（Lock）和同步器（Synchronizer）</p><ul><li><a href="https://github.com/redisson/redisson/wiki/8.-%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E5%92%8C%E5%90%8C%E6%AD%A5%E5%99%A8#81-%E5%8F%AF%E9%87%8D%E5%85%A5%E9%94%81reentrant-lock">8.1. 可重入锁（Reentrant Lock）</a></li><li><a href="https://github.com/redisson/redisson/wiki/8.-%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E5%92%8C%E5%90%8C%E6%AD%A5%E5%99%A8#82-%E5%85%AC%E5%B9%B3%E9%94%81fair-lock">8.2. 公平锁（Fair Lock）</a></li><li><a href="https://github.com/redisson/redisson/wiki/8.-%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E5%92%8C%E5%90%8C%E6%AD%A5%E5%99%A8#83-%E8%81%94%E9%94%81multilock">8.3. 联锁（MultiLock）</a></li><li><a href="https://github.com/redisson/redisson/wiki/8.-%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E5%92%8C%E5%90%8C%E6%AD%A5%E5%99%A8#84-%E7%BA%A2%E9%94%81redlock">8.4. 红锁（RedLock）</a></li><li><a href="https://github.com/redisson/redisson/wiki/8.-%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E5%92%8C%E5%90%8C%E6%AD%A5%E5%99%A8#85-%E8%AF%BB%E5%86%99%E9%94%81readwritelock">8.5. 读写锁（ReadWriteLock）</a></li><li><a href="https://github.com/redisson/redisson/wiki/8.-%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E5%92%8C%E5%90%8C%E6%AD%A5%E5%99%A8#86-%E4%BF%A1%E5%8F%B7%E9%87%8Fsemaphore">8.6. 信号量（Semaphore）</a></li><li><a href="https://github.com/redisson/redisson/wiki/8.-%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E5%92%8C%E5%90%8C%E6%AD%A5%E5%99%A8#87-%E5%8F%AF%E8%BF%87%E6%9C%9F%E6%80%A7%E4%BF%A1%E5%8F%B7%E9%87%8Fpermitexpirablesemaphore">8.7. 可过期性信号量（PermitExpirableSemaphore）</a></li><li><a href="https://github.com/redisson/redisson/wiki/8.-%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E5%92%8C%E5%90%8C%E6%AD%A5%E5%99%A8#88-%E9%97%AD%E9%94%81countdownlatch">8.8. 闭锁（CountDownLatch）</a></li></ul><p>官网地址： <a href="https://redisson.org/">https://redisson.org</a></p><p>GitHub地址： <a href="https://github.com/redisson/redisson">https://github.com/redisson/redisson</a></p><h2 id="❷redisson快速入门"><a href="#❷redisson快速入门" class="headerlink" title="❷redisson快速入门"></a>❷redisson快速入门</h2><p>引入依赖：</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.redisson<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>redisson<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>3.13.6<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>配置Redisson客户端：</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Configuration</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">RedissonConfig</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Bean</span>    <span class="token keyword">public</span> RedissonClient <span class="token function">redissonClient</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 配置</span>        Config config <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Config</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        config<span class="token punctuation">.</span><span class="token function">useSingleServer</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">setAddress</span><span class="token punctuation">(</span><span class="token string">"redis://192.168.150.101:6379"</span><span class="token punctuation">)</span>            <span class="token punctuation">.</span><span class="token function">setPassword</span><span class="token punctuation">(</span><span class="token string">"123321"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 创建RedissonClient对象</span>        <span class="token keyword">return</span> Redisson<span class="token punctuation">.</span><span class="token function">create</span><span class="token punctuation">(</span>config<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>如何使用Redission的分布式锁</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Resource</span><span class="token keyword">private</span> RedissionClient redissonClient<span class="token punctuation">;</span><span class="token annotation punctuation">@Test</span><span class="token keyword">void</span> <span class="token function">testRedisson</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">throws</span> Exception<span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">//获取锁(可重入)，指定锁的名称</span>    RLock lock <span class="token operator">=</span> redissonClient<span class="token punctuation">.</span><span class="token function">getLock</span><span class="token punctuation">(</span><span class="token string">"anyLock"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">//尝试获取锁，参数分别是：获取锁的最大等待时间(期间会重试)，锁自动释放时间，时间单位</span>    <span class="token keyword">boolean</span> isLock <span class="token operator">=</span> lock<span class="token punctuation">.</span><span class="token function">tryLock</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span><span class="token number">10</span><span class="token punctuation">,</span>TimeUnit<span class="token punctuation">.</span>SECONDS<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">//判断获取锁成功</span>    <span class="token keyword">if</span><span class="token punctuation">(</span>isLock<span class="token punctuation">)</span><span class="token punctuation">{</span>        <span class="token keyword">try</span><span class="token punctuation">{</span>            System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"执行业务"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                  <span class="token punctuation">}</span><span class="token keyword">finally</span><span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">//释放锁</span>            lock<span class="token punctuation">.</span><span class="token function">unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>     <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>在 VoucherOrderServiceImpl 注入RedissonClient</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Resource</span><span class="token keyword">private</span> RedissonClient redissonClient<span class="token punctuation">;</span><span class="token annotation punctuation">@Override</span><span class="token keyword">public</span> Result <span class="token function">seckillVoucher</span><span class="token punctuation">(</span>Long voucherId<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 1.查询优惠券</span>        SeckillVoucher voucher <span class="token operator">=</span> seckillVoucherService<span class="token punctuation">.</span><span class="token function">getById</span><span class="token punctuation">(</span>voucherId<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 2.判断秒杀是否开始</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>voucher<span class="token punctuation">.</span><span class="token function">getBeginTime</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">isAfter</span><span class="token punctuation">(</span>LocalDateTime<span class="token punctuation">.</span><span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 尚未开始</span>            <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">fail</span><span class="token punctuation">(</span><span class="token string">"秒杀尚未开始！"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 3.判断秒杀是否已经结束</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>voucher<span class="token punctuation">.</span><span class="token function">getEndTime</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">isBefore</span><span class="token punctuation">(</span>LocalDateTime<span class="token punctuation">.</span><span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 尚未开始</span>            <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">fail</span><span class="token punctuation">(</span><span class="token string">"秒杀已经结束！"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 4.判断库存是否充足</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>voucher<span class="token punctuation">.</span><span class="token function">getStock</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">&lt;</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 库存不足</span>            <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">fail</span><span class="token punctuation">(</span><span class="token string">"库存不足！"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        Long userId <span class="token operator">=</span> UserHolder<span class="token punctuation">.</span><span class="token function">getUser</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">//创建锁对象 这个代码不用了，因为我们现在要使用分布式锁</span>        <span class="token comment" spellcheck="true">//SimpleRedisLock lock = new SimpleRedisLock("order:" + userId, stringRedisTemplate);</span>        RLock lock <span class="token operator">=</span> redissonClient<span class="token punctuation">.</span><span class="token function">getLock</span><span class="token punctuation">(</span><span class="token string">"lock:order:"</span> <span class="token operator">+</span> userId<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">//获取锁对象</span>        <span class="token keyword">boolean</span> isLock <span class="token operator">=</span> lock<span class="token punctuation">.</span><span class="token function">tryLock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">//加锁失败</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>isLock<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">fail</span><span class="token punctuation">(</span><span class="token string">"不允许重复下单"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">try</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">//获取代理对象(事务)</span>            IVoucherOrderService proxy <span class="token operator">=</span> <span class="token punctuation">(</span>IVoucherOrderService<span class="token punctuation">)</span> AopContext<span class="token punctuation">.</span><span class="token function">currentProxy</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">return</span> proxy<span class="token punctuation">.</span><span class="token function">createVoucherOrder</span><span class="token punctuation">(</span>voucherId<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">//释放锁</span>            lock<span class="token punctuation">.</span><span class="token function">unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span> <span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="❸redisson可重入锁原理"><a href="#❸redisson可重入锁原理" class="headerlink" title="❸redisson可重入锁原理"></a>❸redisson可重入锁原理</h2><p><strong>redission可重入锁原理：</strong>采用hash结构用来存储锁，其中KEY表示表示这把锁是否存在（存锁名称），用VALUE表示当前这把锁被哪个线程持有（存线程名和重入状态）。比如当前没有人持有这把锁，那么value&#x3D;0，假如有人持有这把锁，那么value+1，如果持有这把锁的人再次持有（重入）这把锁，那么value就会再+1 ，释放一次就-1 ，直到减少成0时，表示当前这把锁没有被人持有。  </p><p><img src="https://img.jwt1399.top/img/202211272031145.png"></p><p>可重入锁的lua源码</p><table><thead><tr><th>获取锁</th><th>释放锁</th></tr></thead><tbody><tr><td><img src="https://img.jwt1399.top/img/202211272036891.png"></td><td><img src="https://img.jwt1399.top/img/202211272036219.png"></td></tr></tbody></table><h2 id="❹redisson锁重试和WatchDog机制"><a href="#❹redisson锁重试和WatchDog机制" class="headerlink" title="❹redisson锁重试和WatchDog机制"></a>❹redisson锁重试和WatchDog机制</h2><p><img src="https://img.jwt1399.top/img/202211272142836.png" alt="Redisson分布式锁原理"></p><h3 id="1-锁重试"><a href="#1-锁重试" class="headerlink" title="1.锁重试"></a>1.锁重试</h3><p>抢锁过程中，获得当前线程，通过tryAcquire进行抢锁，该抢锁逻辑和之前逻辑相同</p><p>1、先判断当前这把锁是否存在，如果不存在，插入一把锁，返回null</p><p>2、判断当前这把锁是否是属于当前线程，如果是，则返回null</p><p>所以如果返回是null，则代表着当前线程已经抢锁完毕，或者可重入完毕，但是如果以上两个条件都不满足，则进入到第三个条件，返回的是锁的失效时间，源码中你能发现有个while(true) 再次进行tryAcquire进行抢锁</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">//参数分别是：获取锁的最大等待时间(期间会重试)，锁自动释放时间，时间单位</span><span class="token annotation punctuation">@Override</span><span class="token keyword">public</span> <span class="token keyword">boolean</span> <span class="token function">tryLock</span><span class="token punctuation">(</span><span class="token keyword">long</span> waitTime<span class="token punctuation">,</span> <span class="token keyword">long</span> leaseTime<span class="token punctuation">,</span> TimeUnit unit<span class="token punctuation">)</span> <span class="token keyword">throws</span> InterruptedException <span class="token punctuation">{</span>        <span class="token keyword">long</span> time <span class="token operator">=</span> unit<span class="token punctuation">.</span><span class="token function">toMillis</span><span class="token punctuation">(</span>waitTime<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">long</span> current <span class="token operator">=</span> System<span class="token punctuation">.</span><span class="token function">currentTimeMillis</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">long</span> threadId <span class="token operator">=</span> Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        Long ttl <span class="token operator">=</span> <span class="token function">tryAcquire</span><span class="token punctuation">(</span>waitTime<span class="token punctuation">,</span> leaseTime<span class="token punctuation">,</span> unit<span class="token punctuation">,</span> threadId<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// lock acquired</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>ttl <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">//.....</span>                <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>          <span class="token keyword">long</span> currentTime <span class="token operator">=</span> System<span class="token punctuation">.</span><span class="token function">currentTimeMillis</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>          ttl <span class="token operator">=</span> <span class="token function">tryAcquire</span><span class="token punctuation">(</span>waitTime<span class="token punctuation">,</span> leaseTime<span class="token punctuation">,</span> unit<span class="token punctuation">,</span> threadId<span class="token punctuation">)</span><span class="token punctuation">;</span>          <span class="token comment" spellcheck="true">// lock acquired</span>          <span class="token keyword">if</span> <span class="token punctuation">(</span>ttl <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>          <span class="token punctuation">}</span>        <span class="token punctuation">}</span>       <span class="token comment" spellcheck="true">//....</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>通过tryAcquire进行抢锁</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> Long <span class="token function">tryAcquire</span><span class="token punctuation">(</span><span class="token keyword">long</span> waitTime<span class="token punctuation">,</span> <span class="token keyword">long</span> leaseTime<span class="token punctuation">,</span> TimeUnit unit<span class="token punctuation">,</span> <span class="token keyword">long</span> threadId<span class="token punctuation">)</span> <span class="token punctuation">{</span>  <span class="token keyword">return</span> <span class="token function">get</span><span class="token punctuation">(</span><span class="token function">tryAcquireAsync</span><span class="token punctuation">(</span>waitTime<span class="token punctuation">,</span> leaseTime<span class="token punctuation">,</span> unit<span class="token punctuation">,</span> threadId<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><h3 id="2-WatchDog机制"><a href="#2-WatchDog机制" class="headerlink" title="2.WatchDog机制"></a>2.WatchDog机制</h3><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token operator">&lt;</span>T<span class="token operator">></span> RFuture<span class="token operator">&lt;</span>Long<span class="token operator">></span> <span class="token function">tryAcquireAsync</span><span class="token punctuation">(</span><span class="token keyword">long</span> waitTime<span class="token punctuation">,</span> <span class="token keyword">long</span> leaseTime<span class="token punctuation">,</span> TimeUnit unit<span class="token punctuation">,</span> <span class="token keyword">long</span> threadId<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>leaseTime <span class="token operator">!=</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token keyword">return</span> <span class="token function">tryLockInnerAsync</span><span class="token punctuation">(</span>waitTime<span class="token punctuation">,</span> leaseTime<span class="token punctuation">,</span> unit<span class="token punctuation">,</span> threadId<span class="token punctuation">,</span> RedisCommands<span class="token punctuation">.</span>EVAL_LONG<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        RFuture<span class="token operator">&lt;</span>Long<span class="token operator">></span> ttlRemainingFuture <span class="token operator">=</span> <span class="token function">tryLockInnerAsync</span><span class="token punctuation">(</span>waitTime<span class="token punctuation">,</span>                                                commandExecutor<span class="token punctuation">.</span><span class="token function">getConnectionManager</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getCfg</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getLockWatchdogTimeout</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>                                                TimeUnit<span class="token punctuation">.</span>MILLISECONDS<span class="token punctuation">,</span> threadId<span class="token punctuation">,</span> RedisCommands<span class="token punctuation">.</span>EVAL_LONG<span class="token punctuation">)</span><span class="token punctuation">;</span>        ttlRemainingFuture<span class="token punctuation">.</span><span class="token function">onComplete</span><span class="token punctuation">(</span><span class="token punctuation">(</span>ttlRemaining<span class="token punctuation">,</span> e<span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span>e <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token keyword">return</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>            <span class="token comment" spellcheck="true">// lock acquired</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span>ttlRemaining <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token function">scheduleExpirationRenewal</span><span class="token punctuation">(</span>threadId<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> ttlRemainingFuture<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>因为trylock方法有重载方法，一个是带参数，一个是不带参数，如果带参数传入的值是-1，如果传入参数，则leaseTime是他本身，所以如果传入了参数，此时leaseTime !&#x3D; -1 则会进去抢锁，抢锁的逻辑就是之前说的那三个逻辑</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">if</span> <span class="token punctuation">(</span>leaseTime <span class="token operator">!=</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">return</span> <span class="token function">tryLockInnerAsync</span><span class="token punctuation">(</span>waitTime<span class="token punctuation">,</span> leaseTime<span class="token punctuation">,</span> unit<span class="token punctuation">,</span> threadId<span class="token punctuation">,</span> RedisCommands<span class="token punctuation">.</span>EVAL_LONG<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>如果是没有传入时间，也会进行抢锁， 而且抢锁时间是默认看门狗时间</p><ul><li><code>commandExecutor.##.**.getLockWatchdogTimeout()</code></li></ul><p><code>ttlRemainingFuture.onComplete((ttlRemaining, e)</code> 这句话相当于对以上抢锁进行了监听，也就是说当上边抢锁完毕后，此方法会被调用，具体调用的逻辑就是去后台开启一个线程，进行续约逻辑<code>scheduleExpirationRenewal()</code>，也就是看门狗线程</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">scheduleExpirationRenewal</span><span class="token punctuation">(</span><span class="token keyword">long</span> threadId<span class="token punctuation">)</span> <span class="token punctuation">{</span>  ExpirationEntry entry <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ExpirationEntry</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  ExpirationEntry oldEntry <span class="token operator">=</span> EXPIRATION_RENEWAL_MAP<span class="token punctuation">.</span><span class="token function">putIfAbsent</span><span class="token punctuation">(</span><span class="token function">getEntryName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> entry<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span>oldEntry <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>    oldEntry<span class="token punctuation">.</span><span class="token function">addThreadId</span><span class="token punctuation">(</span>threadId<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>    entry<span class="token punctuation">.</span><span class="token function">addThreadId</span><span class="token punctuation">(</span>threadId<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token function">renewExpiration</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>调用renewExpiration()，此逻辑就是续约逻辑</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">renewExpiration</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    ExpirationEntry ee <span class="token operator">=</span> EXPIRATION_RENEWAL_MAP<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token function">getEntryName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>ee <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">return</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>        Timeout task <span class="token operator">=</span> commandExecutor<span class="token punctuation">.</span><span class="token function">getConnectionManager</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">newTimeout</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">TimerTask</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token annotation punctuation">@Override</span>        <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">run</span><span class="token punctuation">(</span>Timeout timeout<span class="token punctuation">)</span> <span class="token keyword">throws</span> Exception <span class="token punctuation">{</span>            ExpirationEntry ent <span class="token operator">=</span> EXPIRATION_RENEWAL_MAP<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token function">getEntryName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span>ent <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token keyword">return</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>            Long threadId <span class="token operator">=</span> ent<span class="token punctuation">.</span><span class="token function">getFirstThreadId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">if</span> <span class="token punctuation">(</span>threadId <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>                <span class="token keyword">return</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>                        RFuture<span class="token operator">&lt;</span>Boolean<span class="token operator">></span> future <span class="token operator">=</span> <span class="token function">renewExpirationAsync</span><span class="token punctuation">(</span>threadId<span class="token punctuation">)</span><span class="token punctuation">;</span>            future<span class="token punctuation">.</span><span class="token function">onComplete</span><span class="token punctuation">(</span><span class="token punctuation">(</span>res<span class="token punctuation">,</span> e<span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span>                <span class="token keyword">if</span> <span class="token punctuation">(</span>e <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>                    log<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token string">"Can't update lock "</span> <span class="token operator">+</span> <span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">" expiration"</span><span class="token punctuation">,</span> e<span class="token punctuation">)</span><span class="token punctuation">;</span>                    <span class="token keyword">return</span><span class="token punctuation">;</span>                <span class="token punctuation">}</span>                                <span class="token keyword">if</span> <span class="token punctuation">(</span>res<span class="token punctuation">)</span> <span class="token punctuation">{</span>                    <span class="token comment" spellcheck="true">// reschedule itself</span>                    <span class="token function">renewExpiration</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token punctuation">}</span>            <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span><span class="token punctuation">,</span> internalLockLeaseTime <span class="token operator">/</span> <span class="token number">3</span><span class="token punctuation">,</span> TimeUnit<span class="token punctuation">.</span>MILLISECONDS<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">//internalLockLeaseTime = 30s</span>    ee<span class="token punctuation">.</span><span class="token function">setTimeout</span><span class="token punctuation">(</span>task<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><code>commandExecutor.getConnectionManager().newTimeout()</code></p><p> 此方法参数：(  new TimerTask() {}，参数2 ，参数3  )</p><p>通过参数2，参数3 去描述什么时候去做参数1的事情，现在的情况是：10s之后去做参数一的事情</p><p>因为锁的失效时间是30s，当10s之后，此时这个timeTask 就触发了，他就去进行续约，把当前这把锁续约成30s，如果操作成功，那么此时就会递归调用自己，再重新设置一个timeTask()，于是再过10s后又再设置一个timerTask，完成不停的续约</p><p>那么大家可以想一想，假设我们的线程出现了宕机他还会续约吗？当然不会，因为没有人再去调用renewExpiration这个方法，所以等到时间之后自然就释放了。</p><h3 id="3-总结"><a href="#3-总结" class="headerlink" title="3.总结"></a>3.总结</h3><p>Redisson分布式锁原理：</p><ul><li><p><strong>可重入</strong>：利用hash结构记录线程id和重入次数</p></li><li><p><strong>可重试</strong>：利用信号量和PubSub功能实现等待、唤醒，获取锁失败的重试机制</p></li><li><p><strong>超时续约</strong>：利用watchDog，每隔一段时间（releaseTime &#x2F; 3），重置超时时间</p></li></ul><h2 id="❺redisson锁的MutiLock原理"><a href="#❺redisson锁的MutiLock原理" class="headerlink" title="❺redisson锁的MutiLock原理"></a>❺redisson锁的MutiLock原理</h2><p>为了提高redis的可用性，我们会搭建集群或者主从，现在以主从为例</p><p>此时我们去写命令，写在主机上， 主机会将数据同步给从机，但是假设在主机还没有来得及把数据写入到从机去的时候，此时主机宕机，哨兵会发现主机宕机，并且选举一个slave变成master，而此时新的master中实际上并没有锁信息，此时锁信息就已经丢掉了。</p><p><img src="https://img.jwt1399.top/img/202211272155896.png"></p><p>为了解决这个问题，redission提出来了<strong>MutiLock锁</strong>，使用这把锁就不使用主从了，每个节点的地位都是一样的， 这把锁加锁的逻辑需要写入到每一个主丛节点上，只有所有的服务器都写入成功，此时才是加锁成功，假设现在某个节点挂了，那么他去获得锁的时候，只要有一个节点拿不到，都不能算是加锁成功，就保证了加锁的可靠性。</p><p><img src="https://img.jwt1399.top/img/202211272155368.png"></p><p>那么MutiLock 加锁原理是什么呢？</p><p>当我们去设置了多个锁时，redission会将多个锁添加到一个集合中，然后用while循环去不停去尝试拿锁，但是会有一个总共的加锁时间，这个时间是用需要加锁的个数 * 1500ms ，假设有3个锁，那么时间就是4500ms，假设在这4500ms内，所有的锁都加锁成功， 那么此时才算是加锁成功，如果在4500ms有线程加锁失败，则会再次去进行重试</p><p><img src="https://img.jwt1399.top/img/202211272200499.png"></p><h2 id="❻总结"><a href="#❻总结" class="headerlink" title="❻总结"></a>❻总结</h2><p><strong>1）不可重入Redis分布式锁：</strong></p><ul><li><p>原理：利用setnx的互斥性；利用ex避免死锁；释放锁时判断线程标示</p></li><li><p>缺陷：不可重入、无法重试、锁超时失效</p></li></ul><p><strong>2）可重入的Redis分布式锁：</strong></p><ul><li><p>原理：利用hash结构，记录线程标示和重入次数；利用watchDog延续锁时间；利用信号量控制锁重试等待</p></li><li><p>缺陷：redis宕机引起锁失效问题</p></li></ul><p><strong>3）Redisson的multiLock：</strong></p><ul><li><p>原理：多个独立的Redis节点，必须在所有节点都获取重入锁，才算获取锁成功</p></li><li><p>缺陷：运维成本高、实现复杂</p></li></ul><h1 id="⑥秒杀优化"><a href="#⑥秒杀优化" class="headerlink" title="⑥秒杀优化"></a>⑥秒杀优化</h1><h2 id="❶异步秒杀思路"><a href="#❶异步秒杀思路" class="headerlink" title="❶异步秒杀思路"></a>❶异步秒杀思路</h2><p>回顾下单流程：当用户发起请求，此时会请求nginx，nginx会访问到tomcat，而tomcat中的程序，会进行串行操作，分成如下几个步骤</p><ul><li><p>1、查询优惠卷</p></li><li><p>2、判断秒杀库存是否足够</p></li><li><p>3、查询订单</p></li><li><p>4、校验是否是一人一单</p></li><li><p>5、扣减库存</p></li><li><p>6、创建订单</p></li></ul><p>在这六步操作中，有很多操作是要去操作数据库的，而且还是一个线程串行执行， 这样就会导致我们的程序执行的很慢，所以我们需要异步程序执行，那么如何加速呢？</p><table><thead><tr><th>原始方案</th><th>优化方案</th></tr></thead><tbody><tr><td><img src="https://img.jwt1399.top/img/202211281342647.png"></td><td><img src="https://img.jwt1399.top/img/202211281342574.png"></td></tr></tbody></table><p>优化方案：我们将耗时比较短的逻辑判断放入到redis中，比如库存是否足够、是否一人一单，只要这种逻辑可以完成，就意味着我们是一定可以下单完成的，我们只需要进行快速的逻辑判断，根本就不用等下单逻辑走完，就可以直接给用户返回成功， 再在后台开一个线程，后台线程慢慢的去执行queue里边的消息，这样程序不就超级快了吗？当然这里边有两个难点</p><ul><li><p>第一个难点是我们怎么在redis中去快速校验一人一单，还有库存判断</p></li><li><p>第二个难点是由于我们校验和下单是两个线程，那么我们如何知道到底哪个单他最后是否成功，或者是下单完成，为了完成这件事我们在redis操作完之后，我们会将一些信息返回给前端，同时也会把这些信息丢到异步queue中去，后续操作中，可以通过这个id来查询我们tomcat中的下单逻辑是否完成了。</p></li></ul><p><img src="https://img.jwt1399.top/img/202211272239865.png"></p><ul><li><p><strong>步骤一：</strong>新增秒杀优惠券的同时，将优惠券信息保存到Redis中</p></li><li><p><strong>步骤二：</strong>基于Lua脚本，判断秒杀库存、一人一单，决定用户是否抢购成功</p></li><li><p><strong>步骤三：</strong>如果抢购成功，将优惠券id、用户id、订单id封装后存入阻塞队列</p></li><li><p><strong>步骤四：</strong>开启线程任务，不断从阻塞队列中获取信息，实现异步下单功能</p></li></ul><h2 id="❷Redis完成秒杀资格判断"><a href="#❷Redis完成秒杀资格判断" class="headerlink" title="❷Redis完成秒杀资格判断"></a>❷Redis完成秒杀资格判断</h2><p><strong>步骤一</strong></p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Override</span><span class="token annotation punctuation">@Transactional</span><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">addSeckillVoucher</span><span class="token punctuation">(</span>Voucher voucher<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 保存优惠券</span>    <span class="token function">save</span><span class="token punctuation">(</span>voucher<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 保存秒杀信息</span>    SeckillVoucher seckillVoucher <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">SeckillVoucher</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    seckillVoucher<span class="token punctuation">.</span><span class="token function">setVoucherId</span><span class="token punctuation">(</span>voucher<span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    seckillVoucher<span class="token punctuation">.</span><span class="token function">setStock</span><span class="token punctuation">(</span>voucher<span class="token punctuation">.</span><span class="token function">getStock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    seckillVoucher<span class="token punctuation">.</span><span class="token function">setBeginTime</span><span class="token punctuation">(</span>voucher<span class="token punctuation">.</span><span class="token function">getBeginTime</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    seckillVoucher<span class="token punctuation">.</span><span class="token function">setEndTime</span><span class="token punctuation">(</span>voucher<span class="token punctuation">.</span><span class="token function">getEndTime</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    seckillVoucherService<span class="token punctuation">.</span><span class="token function">save</span><span class="token punctuation">(</span>seckillVoucher<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 保存秒杀库存到Redis中</span>    stringRedisTemplate<span class="token punctuation">.</span><span class="token function">opsForValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span>SECKILL_STOCK_KEY <span class="token operator">+</span> voucher<span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> voucher<span class="token punctuation">.</span><span class="token function">getStock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤二</strong></p><img src="../images/Redis-实战篇/image-20221128135703650.png" style="zoom:50%;" /><pre class="line-numbers language-lua"><code class="language-lua"><span class="token comment" spellcheck="true">-- 1.参数列表</span><span class="token comment" spellcheck="true">-- 1.1.优惠券id</span><span class="token keyword">local</span> voucherId <span class="token operator">=</span> ARGV<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token comment" spellcheck="true">-- 1.2.用户id</span><span class="token keyword">local</span> userId <span class="token operator">=</span> ARGV<span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span><span class="token comment" spellcheck="true">-- 1.3.订单id</span><span class="token keyword">local</span> orderId <span class="token operator">=</span> ARGV<span class="token punctuation">[</span><span class="token number">3</span><span class="token punctuation">]</span><span class="token comment" spellcheck="true">-- 2.数据key</span><span class="token comment" spellcheck="true">-- 2.1.库存key</span><span class="token keyword">local</span> stockKey <span class="token operator">=</span> <span class="token string">'seckill:stock:'</span> <span class="token operator">..</span> voucherId<span class="token comment" spellcheck="true">-- 2.2.订单key</span><span class="token keyword">local</span> orderKey <span class="token operator">=</span> <span class="token string">'seckill:order:'</span> <span class="token operator">..</span> voucherId<span class="token comment" spellcheck="true">-- 3.脚本业务</span><span class="token comment" spellcheck="true">-- 3.1.判断库存是否充足 get stockKey   </span><span class="token keyword">if</span><span class="token punctuation">(</span><span class="token function">tonumber</span><span class="token punctuation">(</span>redis<span class="token punctuation">.</span><span class="token function">call</span><span class="token punctuation">(</span><span class="token string">'get'</span><span class="token punctuation">,</span> stockKey<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">&lt;=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">then</span> <span class="token comment" spellcheck="true">-- tonumber（string 转 num)</span>    <span class="token comment" spellcheck="true">-- 库存不足，返回1</span>    <span class="token keyword">return</span> <span class="token number">1</span><span class="token keyword">end</span><span class="token comment" spellcheck="true">-- 3.2.判断用户是否下单 SISMEMBER orderKey userId</span><span class="token keyword">if</span><span class="token punctuation">(</span>redis<span class="token punctuation">.</span><span class="token function">call</span><span class="token punctuation">(</span><span class="token string">'sismember'</span><span class="token punctuation">,</span> orderKey<span class="token punctuation">,</span> userId<span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token keyword">then</span>    <span class="token comment" spellcheck="true">-- 3.3.存在，说明是重复下单，返回2</span>    <span class="token keyword">return</span> <span class="token number">2</span><span class="token keyword">end</span><span class="token comment" spellcheck="true">-- 3.4.扣库存 incrby stockKey -1</span>redis<span class="token punctuation">.</span><span class="token function">call</span><span class="token punctuation">(</span><span class="token string">'incrby'</span><span class="token punctuation">,</span> stockKey<span class="token punctuation">,</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true">-- 3.5.下单（保存用户）sadd orderKey userId</span>redis<span class="token punctuation">.</span><span class="token function">call</span><span class="token punctuation">(</span><span class="token string">'sadd'</span><span class="token punctuation">,</span> orderKey<span class="token punctuation">,</span> userId<span class="token punctuation">)</span><span class="token keyword">return</span> <span class="token number">0</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤三</strong></p><img src="../images/Redis-实战篇/image-20221128140938673.png" style="zoom:50%;" /><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// 加载lua脚本</span><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> DefaultRedisScript<span class="token operator">&lt;</span>Long<span class="token operator">></span> SECKILL_SCRIPT<span class="token punctuation">;</span><span class="token keyword">static</span> <span class="token punctuation">{</span>  SECKILL_SCRIPT <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">DefaultRedisScript</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  SECKILL_SCRIPT<span class="token punctuation">.</span><span class="token function">setLocation</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">ClassPathResource</span><span class="token punctuation">(</span><span class="token string">"seckill.lua"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  SECKILL_SCRIPT<span class="token punctuation">.</span><span class="token function">setResultType</span><span class="token punctuation">(</span>Long<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">//阻塞队列</span><span class="token keyword">private</span> BlockingQueue<span class="token operator">&lt;</span>VoucherOrder<span class="token operator">></span> orderTasks <span class="token operator">=</span><span class="token keyword">new</span>  <span class="token class-name">ArrayBlockingQueue</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token number">1024</span> <span class="token operator">*</span> <span class="token number">1024</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">private</span> IVoucherOrderService proxy<span class="token punctuation">;</span>  <span class="token annotation punctuation">@Override</span><span class="token keyword">public</span> Result <span class="token function">seckillVoucher</span><span class="token punctuation">(</span>Long voucherId<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">//获取用户</span>    Long userId <span class="token operator">=</span> UserHolder<span class="token punctuation">.</span><span class="token function">getUser</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">long</span> orderId <span class="token operator">=</span> redisIdWorker<span class="token punctuation">.</span><span class="token function">nextId</span><span class="token punctuation">(</span><span class="token string">"order"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 1.执行lua脚本</span>    Long result <span class="token operator">=</span> stringRedisTemplate<span class="token punctuation">.</span><span class="token function">execute</span><span class="token punctuation">(</span>            SECKILL_SCRIPT<span class="token punctuation">,</span> <span class="token comment" spellcheck="true">//lua脚本</span>            Collections<span class="token punctuation">.</span><span class="token function">emptyList</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>   <span class="token comment" spellcheck="true">//KEY</span>            voucherId<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>     <span class="token comment" spellcheck="true">//ARGV[1]</span>                  userId<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>       <span class="token comment" spellcheck="true">//ARGV[2]</span>                  String<span class="token punctuation">.</span><span class="token function">valueOf</span><span class="token punctuation">(</span>orderId<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">//ARGV[3]</span>    <span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">int</span> r <span class="token operator">=</span> result<span class="token punctuation">.</span><span class="token function">intValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//转成int</span>    <span class="token comment" spellcheck="true">// 2.判断结果是否为0</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>r <span class="token operator">!=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 2.1.不为0 ，代表没有购买资格</span>        <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">fail</span><span class="token punctuation">(</span>r <span class="token operator">==</span> <span class="token number">1</span> <span class="token operator">?</span> <span class="token string">"库存不足"</span> <span class="token operator">:</span> <span class="token string">"不能重复下单"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>          <span class="token comment" spellcheck="true">//2.2.为0，信息保存到阻塞队列</span>    VoucherOrder voucherOrder <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">VoucherOrder</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 2.2.1订单id</span>    <span class="token keyword">long</span> orderId <span class="token operator">=</span> redisIdWorker<span class="token punctuation">.</span><span class="token function">nextId</span><span class="token punctuation">(</span><span class="token string">"order"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    voucherOrder<span class="token punctuation">.</span><span class="token function">setId</span><span class="token punctuation">(</span>orderId<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 2.2.2用户id</span>    voucherOrder<span class="token punctuation">.</span><span class="token function">setUserId</span><span class="token punctuation">(</span>userId<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 2.2.3代金券id</span>    voucherOrder<span class="token punctuation">.</span><span class="token function">setVoucherId</span><span class="token punctuation">(</span>voucherId<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 2.2.4放入阻塞队列</span>    orderTasks<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>voucherOrder<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 3.获取代理对象</span>    proxy <span class="token operator">=</span> <span class="token punctuation">(</span>IVoucherOrderService<span class="token punctuation">)</span>AopContext<span class="token punctuation">.</span><span class="token function">currentProxy</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token comment" spellcheck="true">// 4.返回订单id</span>    <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">ok</span><span class="token punctuation">(</span>orderId<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="❸基于阻塞队列实现秒杀优化"><a href="#❸基于阻塞队列实现秒杀优化" class="headerlink" title="❸基于阻塞队列实现秒杀优化"></a>❸基于阻塞队列实现秒杀优化</h2><p><strong>步骤四</strong></p><p>现在我们去下单时，是通过lua表达式去原子执行判断逻辑，如果判断我出来不为0 ，则要么是库存不足，要么是重复下单，返回错误信息；如果是0，则把下单的逻辑保存到队列中去，然后异步执行</p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">//异步处理线程池</span><span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> ExecutorService SECKILL_ORDER_EXECUTOR <span class="token operator">=</span> Executors<span class="token punctuation">.</span><span class="token function">newSingleThreadExecutor</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//在类初始化之后执行，因为当这个类初始化好了之后，随时都是有可能要执行的</span><span class="token annotation punctuation">@PostConstruct</span><span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">init</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>  SECKILL_ORDER_EXECUTOR<span class="token punctuation">.</span><span class="token function">submit</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">VoucherOrderHandler</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">// 用于线程池处理的任务</span><span class="token comment" spellcheck="true">// 当初始化完毕后，就会去从对列中去拿信息</span><span class="token keyword">private</span> <span class="token keyword">class</span> <span class="token class-name">VoucherOrderHandler</span> <span class="token keyword">implements</span> <span class="token class-name">Runnable</span> <span class="token punctuation">{</span>  <span class="token annotation punctuation">@Override</span>  <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">run</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>      <span class="token keyword">try</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 1.获取队列中的订单信息</span>        VoucherOrder voucherOrder <span class="token operator">=</span> orderTasks<span class="token punctuation">.</span><span class="token function">take</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 2.创建订单</span>        <span class="token function">handleVoucherOrder</span><span class="token punctuation">(</span>voucherOrder<span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Exception</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span>        log<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token string">"处理订单异常"</span><span class="token punctuation">,</span> e<span class="token punctuation">)</span><span class="token punctuation">;</span>      <span class="token punctuation">}</span>    <span class="token punctuation">}</span>  <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">// 创建订单</span><span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">handleVoucherOrder</span><span class="token punctuation">(</span>VoucherOrder voucherOrder<span class="token punctuation">)</span> <span class="token punctuation">{</span>  <span class="token comment" spellcheck="true">//1.获取用户</span>  Long userId <span class="token operator">=</span> voucherOrder<span class="token punctuation">.</span><span class="token function">getUserId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment" spellcheck="true">// 2.创建锁对象</span>  RLock lock <span class="token operator">=</span> redissonClient<span class="token punctuation">.</span><span class="token function">getLock</span><span class="token punctuation">(</span><span class="token string">"lock:order:"</span> <span class="token operator">+</span> userId<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment" spellcheck="true">// 3.尝试获取锁</span>  <span class="token keyword">boolean</span> isLock <span class="token operator">=</span> lock<span class="token punctuation">.</span><span class="token function">tryLock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment" spellcheck="true">// 4.判断是否获得锁成功</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>isLock<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 获取锁失败，直接返回失败或者重试</span>    log<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token string">"不允许重复下单！"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span><span class="token punctuation">;</span>  <span class="token punctuation">}</span>  <span class="token keyword">try</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">//注意：由于是spring的事务是放在threadLocal中，此时的是多线程，事务会失效</span>    proxy<span class="token punctuation">.</span><span class="token function">createVoucherOrder</span><span class="token punctuation">(</span>voucherOrder<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 释放锁</span>    lock<span class="token punctuation">.</span><span class="token function">unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token annotation punctuation">@Transactional</span><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">createVoucherOrder</span><span class="token punctuation">(</span>VoucherOrder voucherOrder<span class="token punctuation">)</span> <span class="token punctuation">{</span>  Long userId <span class="token operator">=</span> voucherOrder<span class="token punctuation">.</span><span class="token function">getUserId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment" spellcheck="true">// 5.1.查询订单</span>  <span class="token keyword">int</span> count <span class="token operator">=</span> <span class="token function">query</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">eq</span><span class="token punctuation">(</span><span class="token string">"user_id"</span><span class="token punctuation">,</span> userId<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">eq</span><span class="token punctuation">(</span><span class="token string">"voucher_id"</span><span class="token punctuation">,</span> voucherOrder<span class="token punctuation">.</span><span class="token function">getVoucherId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">count</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment" spellcheck="true">// 5.2.判断是否存在</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span>count <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 用户已经购买过了</span>    log<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token string">"用户已经购买过了"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> <span class="token punctuation">;</span>  <span class="token punctuation">}</span>  <span class="token comment" spellcheck="true">// 6.扣减库存</span>  <span class="token keyword">boolean</span> success <span class="token operator">=</span> seckillVoucherService<span class="token punctuation">.</span><span class="token function">update</span><span class="token punctuation">(</span><span class="token punctuation">)</span>    <span class="token punctuation">.</span><span class="token function">setSql</span><span class="token punctuation">(</span><span class="token string">"stock = stock - 1"</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// set stock = stock - 1</span>    <span class="token punctuation">.</span><span class="token function">eq</span><span class="token punctuation">(</span><span class="token string">"voucher_id"</span><span class="token punctuation">,</span> voucherOrder<span class="token punctuation">.</span><span class="token function">getVoucherId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">gt</span><span class="token punctuation">(</span><span class="token string">"stock"</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">//where id = ? and stock > 0</span>    <span class="token punctuation">.</span><span class="token function">update</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>success<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 扣减失败</span>    log<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token string">"库存不足"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> <span class="token punctuation">;</span>  <span class="token punctuation">}</span>  <span class="token function">save</span><span class="token punctuation">(</span>voucherOrder<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="❹总结"><a href="#❹总结" class="headerlink" title="❹总结"></a>❹总结</h2><p>秒杀业务的优化思路是什么？</p><ul><li>先利用Redis完成库存余量、一人一单判断，完成抢单业务</li><li>再将下单业务放入阻塞队列，利用独立线程异步下单</li><li>基于阻塞队列的异步秒杀存在哪些问题？<ul><li>内存限制问题</li><li>数据安全问题</li></ul></li></ul><h1 id="⑦Redis消息队列"><a href="#⑦Redis消息队列" class="headerlink" title="⑦Redis消息队列"></a>⑦Redis消息队列</h1><h2 id="❶认识消息队列"><a href="#❶认识消息队列" class="headerlink" title="❶认识消息队列"></a>❶认识消息队列</h2><p>什么是消息队列：存放消息的队列。最简单的消息队列模型包括3个角色：</p><ul><li>消息队列：存储和管理消息，也被称为消息代理（Message Broker）</li><li>生产者：发送消息到消息队列</li><li>消费者：从消息队列获取消息并处理消息</li></ul><p>使用队列的好处在于 <strong>解耦：</strong>所谓解耦，举一个生活中的例子就是：快递员(生产者)把快递放到快递柜里边(Message Queue)去，我们(消费者)从快递柜里边去拿东西，这就是一个异步，如果耦合，那么这个快递员相当于直接把快递交给你，这事固然好，但是万一你不在家，那么快递员就会一直等你，这就浪费了快递员的时间，所以这种思想在我们日常开发中，是非常有必要的。</p><p><img src="https://img.jwt1399.top/img/202211281835719.png"></p><p>这种场景在我们秒杀中就变成了：我们下单之后，利用redis去进行校验下单条件，再通过队列把消息发送出去，然后再启动一个线程去消费这个消息，完成解耦，同时也加快我们的响应速度。</p><p>这里我们可以使用一些现成的mq，比如kafka，rabbitmq等等，但是呢，如果没有安装mq，我们也可以直接使用redis提供的mq方案，降低我们的部署和学习成本。</p><h2 id="❷基于List实现消息队列"><a href="#❷基于List实现消息队列" class="headerlink" title="❷基于List实现消息队列"></a>❷基于List实现消息队列</h2><p>Redis的List数据结构是一个双向链表，很容易模拟出队列效果。</p><p>队列是入口和出口不在一边，因此我们可以利用：LPUSH 结合 RPOP、或者 RPUSH 结合 LPOP来实现。<br>不过要注意的是，当队列中没有消息时RPOP或LPOP操作会返回null，并不像JVM的阻塞队列那样会阻塞并等待消息。因此这里应该使用BRPOP或者BLPOP来实现阻塞效果。</p><p><img src="https://img.jwt1399.top/img/202211281839415.png"></p><p>基于List的消息队列有哪些优缺点？</p><p>优点：</p><ul><li>利用Redis存储，不受限于JVM内存上限</li><li>基于Redis的持久化机制，数据安全性有保证</li><li>可以满足消息有序性</li></ul><p>缺点：</p><ul><li>无法避免消息丢失</li><li>只支持单消费者</li></ul><h2 id="❸基于PubSub的消息队列"><a href="#❸基于PubSub的消息队列" class="headerlink" title="❸基于PubSub的消息队列"></a>❸基于PubSub的消息队列</h2><p>PubSub（发布订阅）是Redis2.0版本引入的消息传递模型。消费者可以订阅一个或多个channel，生产者向对应channel发送消息后，所有订阅者都能收到相关消息。</p><ul><li>SUBSCRIBE channel [channel] ：订阅一个或多个频道</li><li>PUBLISH channel msg ：向一个频道发送消息</li><li>PSUBSCRIBE pattern[pattern] ：订阅与pattern格式匹配的所有频道</li></ul><p><img src="https://img.jwt1399.top/img/202211281840014.png"></p><p>基于PubSub的消息队列有哪些优缺点？<br>优点：</p><ul><li>采用发布订阅模型，支持多生产、多消费</li></ul><p>缺点：</p><ul><li>不支持数据持久化</li><li>无法避免消息丢失</li><li>消息堆积有上限，超出时数据丢失</li></ul><h2 id="❹基于Stream的消息队列"><a href="#❹基于Stream的消息队列" class="headerlink" title="❹基于Stream的消息队列"></a>❹基于Stream的消息队列</h2><p>Stream 是 Redis 5.0 引入的一种新数据类型，可以实现一个功能非常完善的消息队列。</p><p>发送消息的命令：XADD</p><p><img src="https://img.jwt1399.top/img/202211281903260.png"></p><p>例如：</p><p><img src="https://img.jwt1399.top/img/202211281903875.png"></p><p>读取消息的方式之一：XREAD</p><p><img src="https://img.jwt1399.top/img/202211281905063.png"></p><p>例如，使用XREAD读取第一个消息：</p><p><img src="https://img.jwt1399.top/img/202211281905688.png"></p><p>例如，XREAD阻塞方式，读取最新的消息：</p><p><img src="https://img.jwt1399.top/img/202211281905433.png"></p><p>在业务开发中，我们可以循环的调用XREAD阻塞方式来查询最新消息，从而实现持续监听队列的效果，伪代码如下</p><p><img src="https://img.jwt1399.top/img/202211281907873.png"></p><p>注意：当我们指定起始ID为$时，代表读取最新的消息，如果我们处理一条消息的过程中，又有超过1条以上的消息到达队列，则下次获取时也只能获取到最新的一条，会出现漏读消息的问题</p><p>STREAM类型消息队列的XREAD命令特点：</p><ul><li>消息可回溯（消息可重复读）</li><li>一个消息可以被多个消费者读取</li><li>可以阻塞读取</li><li>有消息漏读的风险</li></ul><h2 id="❺基于Stream的消息队列-消费者组"><a href="#❺基于Stream的消息队列-消费者组" class="headerlink" title="❺基于Stream的消息队列-消费者组"></a>❺基于Stream的消息队列-消费者组</h2><p>消费者组（Consumer Group）：将多个消费者划分到一个组中，监听同一个队列。具备下列特点：</p><p><img src="https://img.jwt1399.top/img/202211281930845.png"></p><p><strong>创建消费者组：</strong></p><pre class="line-numbers language-sql"><code class="language-sql">XGROUP <span class="token keyword">CREATE</span> <span class="token keyword">key</span> groupName ID <span class="token punctuation">[</span>MKSTREAM<span class="token punctuation">]</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><ul><li>key：队列名称</li><li>groupName：消费者组名称</li><li>ID：起始ID标示，$代表队列中最后一个消息，0则代表队列中第一个消息</li><li>MKSTREAM：队列不存在时自动创建队列</li></ul><p> <strong>删除指定的消费者组</strong></p><pre class="line-numbers language-sql"><code class="language-sql">XGROUP DESTORY <span class="token keyword">key</span> groupName<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p> <strong>给指定的消费者组添加消费者</strong></p><pre class="line-numbers language-sql"><code class="language-sql">XGROUP CREATECONSUMER <span class="token keyword">key</span> groupName consumername<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p> <strong>删除消费者组中的指定消费者</strong></p><pre class="line-numbers language-sql"><code class="language-sql">XGROUP DELCONSUMER <span class="token keyword">key</span> groupName consumername<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p><strong>从消费者组读取消息</strong></p><pre class="line-numbers language-sql"><code class="language-sql">XREADGROUP <span class="token keyword">GROUP</span> <span class="token keyword">group</span> consumer <span class="token punctuation">[</span>COUNT count<span class="token punctuation">]</span> <span class="token punctuation">[</span>BLOCK milliseconds<span class="token punctuation">]</span> <span class="token punctuation">[</span>NOACK<span class="token punctuation">]</span> STREAMS <span class="token keyword">key</span> <span class="token punctuation">[</span><span class="token keyword">key</span> <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">]</span> ID <span class="token punctuation">[</span>ID <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">]</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><ul><li>group：消费组名称</li><li>consumer：消费者名称，如果消费者不存在，会自动创建一个消费者</li><li>count：本次查询的最大数量</li><li>BLOCK milliseconds：当没有消息时最长等待时间</li><li>NOACK：无需手动ACK，获取到消息后自动确认</li><li>STREAMS key：指定队列名称</li><li>ID：获取消息的起始ID：<ul><li>“&gt;”：从下一个未消费的消息开始</li><li>其它：根据指定id从pending-list中获取已消费但未确认的消息，例如0，是从pending-list中的第一个消息开始</li></ul></li></ul><p>消费者监听消息的基本思路：伪代码如下</p><p><img src="https://img.jwt1399.top/img/202211281947654.png"></p><p><strong>总结</strong></p><p>STREAM类型消息队列的XREADGROUP命令特点：</p><ul><li>消息可回溯</li><li>可以多消费者争抢消息，加快消费速度</li><li>可以阻塞读取</li><li>没有消息漏读的风险</li><li>有消息确认机制，保证消息至少被消费一次</li></ul><p>最后我们来个小对比</p><p><img src="https://img.jwt1399.top/img/202211281940958.png"></p><h2 id="❻Stream消息队列实现异步秒杀下单"><a href="#❻Stream消息队列实现异步秒杀下单" class="headerlink" title="❻Stream消息队列实现异步秒杀下单"></a>❻Stream消息队列实现异步秒杀下单</h2><ul><li>创建一个Stream类型的消息队列，名为stream.orders</li></ul><pre class="line-numbers language-sql"><code class="language-sql">XGROUP <span class="token keyword">CREATE</span> stream<span class="token punctuation">.</span>orders g1 <span class="token number">0</span> MKSTREAM<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><ul><li>修改之前的秒杀下单Lua脚本（seckill.lua），在认定有抢购资格后，直接向stream.orders中添加消息，内容包含voucherId、userId、orderId</li></ul><pre class="line-numbers language-sql"><code class="language-sql"><span class="token comment" spellcheck="true">-- 3.6.发送消息到队列中， XADD stream.orders * k1 v1 k2 v2 ...</span>redis<span class="token punctuation">.</span><span class="token keyword">call</span><span class="token punctuation">(</span><span class="token string">'xadd'</span><span class="token punctuation">,</span> <span class="token string">'stream.orders'</span><span class="token punctuation">,</span> <span class="token string">'*'</span><span class="token punctuation">,</span> <span class="token string">'userId'</span><span class="token punctuation">,</span> userId<span class="token punctuation">,</span> <span class="token string">'voucherId'</span><span class="token punctuation">,</span> voucherId<span class="token punctuation">,</span> <span class="token string">'id'</span><span class="token punctuation">,</span> orderId<span class="token punctuation">)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Override</span><span class="token keyword">public</span> Result <span class="token function">seckillVoucher</span><span class="token punctuation">(</span>Long voucherId<span class="token punctuation">)</span> <span class="token punctuation">{</span>  <span class="token comment" spellcheck="true">//获取用户</span>  Long userId <span class="token operator">=</span> UserHolder<span class="token punctuation">.</span><span class="token function">getUser</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">long</span> orderId <span class="token operator">=</span> redisIdWorker<span class="token punctuation">.</span><span class="token function">nextId</span><span class="token punctuation">(</span><span class="token string">"order"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment" spellcheck="true">// 1.执行lua脚本</span>  Long result <span class="token operator">=</span> stringRedisTemplate<span class="token punctuation">.</span><span class="token function">execute</span><span class="token punctuation">(</span>    SECKILL_SCRIPT<span class="token punctuation">,</span> <span class="token comment" spellcheck="true">//lua脚本</span>    Collections<span class="token punctuation">.</span><span class="token function">emptyList</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>   <span class="token comment" spellcheck="true">//KEY</span>    voucherId<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>     <span class="token comment" spellcheck="true">//ARGV[1]</span>    userId<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>       <span class="token comment" spellcheck="true">//ARGV[2]</span>    String<span class="token punctuation">.</span><span class="token function">valueOf</span><span class="token punctuation">(</span>orderId<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">//ARGV[3]</span>  <span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">int</span> r <span class="token operator">=</span> result<span class="token punctuation">.</span><span class="token function">intValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//转成int</span>  <span class="token comment" spellcheck="true">// 2.判断结果是否为0</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span>r <span class="token operator">!=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 2.1.不为0 ，代表没有购买资格</span>    <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">fail</span><span class="token punctuation">(</span>r <span class="token operator">==</span> <span class="token number">1</span> <span class="token operator">?</span> <span class="token string">"库存不足"</span> <span class="token operator">:</span> <span class="token string">"不能重复下单"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">}</span>  <span class="token comment" spellcheck="true">// 3.获取代理对象</span>  proxy <span class="token operator">=</span> <span class="token punctuation">(</span>IVoucherOrderService<span class="token punctuation">)</span>AopContext<span class="token punctuation">.</span><span class="token function">currentProxy</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment" spellcheck="true">// 4.返回订单id</span>  <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">ok</span><span class="token punctuation">(</span>orderId<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>项目启动时，开启一个线程任务，尝试获取stream.orders中的消息，完成下单</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">class</span> <span class="token class-name">VoucherOrderHandler</span> <span class="token keyword">implements</span> <span class="token class-name">Runnable</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">run</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token keyword">try</span> <span class="token punctuation">{</span>                <span class="token comment" spellcheck="true">// 1.获取消息队列中的订单信息 </span>                <span class="token comment" spellcheck="true">// XREADGROUP GROUP g1 c1 COUNT 1 BLOCK 2000 STREAMS s1 ></span>                List<span class="token operator">&lt;</span>MapRecord<span class="token operator">&lt;</span>String<span class="token punctuation">,</span> Object<span class="token punctuation">,</span> Object<span class="token operator">>></span> list <span class="token operator">=</span> stringRedisTemplate<span class="token punctuation">.</span><span class="token function">opsForStream</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">read</span><span class="token punctuation">(</span>                    Consumer<span class="token punctuation">.</span><span class="token function">from</span><span class="token punctuation">(</span><span class="token string">"g1"</span><span class="token punctuation">,</span> <span class="token string">"c1"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>                    StreamReadOptions<span class="token punctuation">.</span><span class="token function">empty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">count</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">block</span><span class="token punctuation">(</span>Duration<span class="token punctuation">.</span><span class="token function">ofSeconds</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span>                    StreamOffset<span class="token punctuation">.</span><span class="token function">create</span><span class="token punctuation">(</span><span class="token string">"stream.orders"</span><span class="token punctuation">,</span> ReadOffset<span class="token punctuation">.</span><span class="token function">lastConsumed</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>                <span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token comment" spellcheck="true">// 2.判断订单信息是否为空</span>                <span class="token keyword">if</span> <span class="token punctuation">(</span>list <span class="token operator">==</span> null <span class="token operator">||</span> list<span class="token punctuation">.</span><span class="token function">isEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                    <span class="token comment" spellcheck="true">// 如果为null，说明没有消息，继续下一次循环</span>                    <span class="token keyword">continue</span><span class="token punctuation">;</span>                <span class="token punctuation">}</span>                <span class="token comment" spellcheck="true">// 解析数据</span>                MapRecord<span class="token operator">&lt;</span>String<span class="token punctuation">,</span> Object<span class="token punctuation">,</span> Object<span class="token operator">></span> record <span class="token operator">=</span> list<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                Map<span class="token operator">&lt;</span>Object<span class="token punctuation">,</span> Object<span class="token operator">></span> value <span class="token operator">=</span> record<span class="token punctuation">.</span><span class="token function">getValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                VoucherOrder voucherOrder <span class="token operator">=</span> BeanUtil<span class="token punctuation">.</span><span class="token function">fillBeanWithMap</span><span class="token punctuation">(</span>value<span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name">VoucherOrder</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token comment" spellcheck="true">// 3.创建订单</span>                <span class="token function">createVoucherOrder</span><span class="token punctuation">(</span>voucherOrder<span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token comment" spellcheck="true">// 4.确认消息 XACK</span>                stringRedisTemplate<span class="token punctuation">.</span><span class="token function">opsForStream</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">acknowledge</span><span class="token punctuation">(</span><span class="token string">"s1"</span><span class="token punctuation">,</span> <span class="token string">"g1"</span><span class="token punctuation">,</span> record<span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Exception</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span>                log<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token string">"处理订单异常"</span><span class="token punctuation">,</span> e<span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token comment" spellcheck="true">//处理异常消息</span>                <span class="token function">handlePendingList</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span>    <span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">handlePendingList</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token keyword">try</span> <span class="token punctuation">{</span>                <span class="token comment" spellcheck="true">// 1.获取pending-list中的订单信息 </span>               <span class="token comment" spellcheck="true">// XREADGROUP GROUP g1 c1 COUNT 1 BLOCK 2000 STREAMS s1 0</span>                List<span class="token operator">&lt;</span>MapRecord<span class="token operator">&lt;</span>String<span class="token punctuation">,</span> Object<span class="token punctuation">,</span> Object<span class="token operator">>></span> list <span class="token operator">=</span> stringRedisTemplate<span class="token punctuation">.</span><span class="token function">opsForStream</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">read</span><span class="token punctuation">(</span>                    Consumer<span class="token punctuation">.</span><span class="token function">from</span><span class="token punctuation">(</span><span class="token string">"g1"</span><span class="token punctuation">,</span> <span class="token string">"c1"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>                    StreamReadOptions<span class="token punctuation">.</span><span class="token function">empty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">count</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">,</span>                    StreamOffset<span class="token punctuation">.</span><span class="token function">create</span><span class="token punctuation">(</span><span class="token string">"stream.orders"</span><span class="token punctuation">,</span> ReadOffset<span class="token punctuation">.</span><span class="token function">from</span><span class="token punctuation">(</span><span class="token string">"0"</span><span class="token punctuation">)</span><span class="token punctuation">)</span>                <span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token comment" spellcheck="true">// 2.判断订单信息是否为空</span>                <span class="token keyword">if</span> <span class="token punctuation">(</span>list <span class="token operator">==</span> null <span class="token operator">||</span> list<span class="token punctuation">.</span><span class="token function">isEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>                    <span class="token comment" spellcheck="true">// 如果为null，说明没有异常消息，结束循环</span>                    <span class="token keyword">break</span><span class="token punctuation">;</span>                <span class="token punctuation">}</span>                <span class="token comment" spellcheck="true">// 解析数据</span>                MapRecord<span class="token operator">&lt;</span>String<span class="token punctuation">,</span> Object<span class="token punctuation">,</span> Object<span class="token operator">></span> record <span class="token operator">=</span> list<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                Map<span class="token operator">&lt;</span>Object<span class="token punctuation">,</span> Object<span class="token operator">></span> value <span class="token operator">=</span> record<span class="token punctuation">.</span><span class="token function">getValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                VoucherOrder voucherOrder <span class="token operator">=</span> BeanUtil<span class="token punctuation">.</span><span class="token function">fillBeanWithMap</span><span class="token punctuation">(</span>value<span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name">VoucherOrder</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token comment" spellcheck="true">// 3.创建订单</span>                <span class="token function">createVoucherOrder</span><span class="token punctuation">(</span>voucherOrder<span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token comment" spellcheck="true">// 4.确认消息 XACK</span>                stringRedisTemplate<span class="token punctuation">.</span><span class="token function">opsForStream</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">acknowledge</span><span class="token punctuation">(</span><span class="token string">"s1"</span><span class="token punctuation">,</span> <span class="token string">"g1"</span><span class="token punctuation">,</span> record<span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Exception</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span>                log<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token string">"处理pendding订单异常"</span><span class="token punctuation">,</span> e<span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token keyword">try</span><span class="token punctuation">{</span>                    Thread<span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">20</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token punctuation">}</span><span class="token keyword">catch</span><span class="token punctuation">(</span>Exception e<span class="token punctuation">)</span><span class="token punctuation">{</span>                    e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                <span class="token punctuation">}</span>            <span class="token punctuation">}</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h1 id="⑧达人探店-ZSet"><a href="#⑧达人探店-ZSet" class="headerlink" title="⑧达人探店-ZSet"></a>⑧达人探店-ZSet</h1><h2 id="❶发布探店笔记"><a href="#❶发布探店笔记" class="headerlink" title="❶发布探店笔记"></a>❶发布探店笔记</h2><p>探店笔记类似点评网站的评价，往往是图文结合。对应的表有两个：</p><ul><li>tb_blog：探店笔记表，包含笔记中的标题、文字、图片等</li><li>tb_blog_comments：其他用户对探店笔记的评价</li></ul><pre class="line-numbers language-sql"><code class="language-sql"><span class="token keyword">CREATE</span> <span class="token keyword">TABLE</span> <span class="token punctuation">`</span>tb_blog<span class="token punctuation">`</span> <span class="token punctuation">(</span>  <span class="token punctuation">`</span>id<span class="token punctuation">`</span> <span class="token keyword">bigint</span><span class="token punctuation">(</span><span class="token number">20</span><span class="token punctuation">)</span> unsigned <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">AUTO_INCREMENT</span> <span class="token keyword">COMMENT</span> <span class="token string">'主键'</span><span class="token punctuation">,</span>  <span class="token punctuation">`</span>shop_id<span class="token punctuation">`</span> <span class="token keyword">bigint</span><span class="token punctuation">(</span><span class="token number">20</span><span class="token punctuation">)</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">COMMENT</span> <span class="token string">'商户id'</span><span class="token punctuation">,</span>  <span class="token punctuation">`</span>user_id<span class="token punctuation">`</span> <span class="token keyword">bigint</span><span class="token punctuation">(</span><span class="token number">20</span><span class="token punctuation">)</span> unsigned <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">COMMENT</span> <span class="token string">'用户id'</span><span class="token punctuation">,</span>  <span class="token punctuation">`</span>title<span class="token punctuation">`</span> <span class="token keyword">varchar</span><span class="token punctuation">(</span><span class="token number">255</span><span class="token punctuation">)</span> <span class="token keyword">CHARACTER SET</span> utf8mb4 <span class="token keyword">COLLATE</span> utf8mb4_unicode_ci <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">COMMENT</span> <span class="token string">'标题'</span><span class="token punctuation">,</span>  <span class="token punctuation">`</span>images<span class="token punctuation">`</span> <span class="token keyword">varchar</span><span class="token punctuation">(</span><span class="token number">2048</span><span class="token punctuation">)</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">COMMENT</span> <span class="token string">'探店的照片，最多9张，多张以","隔开'</span><span class="token punctuation">,</span>  <span class="token punctuation">`</span>content<span class="token punctuation">`</span> <span class="token keyword">varchar</span><span class="token punctuation">(</span><span class="token number">2048</span><span class="token punctuation">)</span> <span class="token keyword">CHARACTER SET</span> utf8mb4 <span class="token keyword">COLLATE</span> utf8mb4_unicode_ci <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">COMMENT</span> <span class="token string">'探店的文字描述'</span><span class="token punctuation">,</span>  <span class="token punctuation">`</span>liked<span class="token punctuation">`</span> <span class="token keyword">int</span><span class="token punctuation">(</span><span class="token number">8</span><span class="token punctuation">)</span> unsigned <span class="token keyword">DEFAULT</span> <span class="token string">'0'</span> <span class="token keyword">COMMENT</span> <span class="token string">'点赞数量'</span><span class="token punctuation">,</span>  <span class="token punctuation">`</span>comments<span class="token punctuation">`</span> <span class="token keyword">int</span><span class="token punctuation">(</span><span class="token number">8</span><span class="token punctuation">)</span> unsigned <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span> <span class="token keyword">COMMENT</span> <span class="token string">'评论数量'</span><span class="token punctuation">,</span>  <span class="token punctuation">`</span>create_time<span class="token punctuation">`</span> <span class="token keyword">timestamp</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token keyword">CURRENT_TIMESTAMP</span> <span class="token keyword">COMMENT</span> <span class="token string">'创建时间'</span><span class="token punctuation">,</span>  <span class="token punctuation">`</span>update_time<span class="token punctuation">`</span> <span class="token keyword">timestamp</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token keyword">CURRENT_TIMESTAMP</span> <span class="token keyword">ON</span> <span class="token keyword">UPDATE</span> <span class="token keyword">CURRENT_TIMESTAMP</span> <span class="token keyword">COMMENT</span> <span class="token string">'更新时间'</span><span class="token punctuation">,</span>  <span class="token keyword">PRIMARY</span> <span class="token keyword">KEY</span> <span class="token punctuation">(</span><span class="token punctuation">`</span>id<span class="token punctuation">`</span><span class="token punctuation">)</span> <span class="token keyword">USING</span> <span class="token keyword">BTREE</span><span class="token punctuation">)</span> <span class="token keyword">ENGINE</span><span class="token operator">=</span><span class="token keyword">InnoDB</span> <span class="token keyword">AUTO_INCREMENT</span><span class="token operator">=</span><span class="token number">8</span> <span class="token keyword">DEFAULT</span> <span class="token keyword">CHARSET</span><span class="token operator">=</span>utf8mb4 ROW_FORMAT<span class="token operator">=</span>COMPACT<span class="token punctuation">;</span><span class="token keyword">CREATE</span> <span class="token keyword">TABLE</span> <span class="token punctuation">`</span>tb_blog_comments<span class="token punctuation">`</span> <span class="token punctuation">(</span>  <span class="token punctuation">`</span>id<span class="token punctuation">`</span> <span class="token keyword">bigint</span><span class="token punctuation">(</span><span class="token number">20</span><span class="token punctuation">)</span> unsigned <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">AUTO_INCREMENT</span> <span class="token keyword">COMMENT</span> <span class="token string">'主键'</span><span class="token punctuation">,</span>  <span class="token punctuation">`</span>user_id<span class="token punctuation">`</span> <span class="token keyword">bigint</span><span class="token punctuation">(</span><span class="token number">20</span><span class="token punctuation">)</span> unsigned <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">COMMENT</span> <span class="token string">'用户id'</span><span class="token punctuation">,</span>  <span class="token punctuation">`</span>blog_id<span class="token punctuation">`</span> <span class="token keyword">bigint</span><span class="token punctuation">(</span><span class="token number">20</span><span class="token punctuation">)</span> unsigned <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">COMMENT</span> <span class="token string">'探店id'</span><span class="token punctuation">,</span>  <span class="token punctuation">`</span>parent_id<span class="token punctuation">`</span> <span class="token keyword">bigint</span><span class="token punctuation">(</span><span class="token number">20</span><span class="token punctuation">)</span> unsigned <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">COMMENT</span> <span class="token string">'关联的1级评论id，如果是一级评论，则值为0'</span><span class="token punctuation">,</span>  <span class="token punctuation">`</span>answer_id<span class="token punctuation">`</span> <span class="token keyword">bigint</span><span class="token punctuation">(</span><span class="token number">20</span><span class="token punctuation">)</span> unsigned <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">COMMENT</span> <span class="token string">'回复的评论id'</span><span class="token punctuation">,</span>  <span class="token punctuation">`</span>content<span class="token punctuation">`</span> <span class="token keyword">varchar</span><span class="token punctuation">(</span><span class="token number">255</span><span class="token punctuation">)</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">COMMENT</span> <span class="token string">'回复的内容'</span><span class="token punctuation">,</span>  <span class="token punctuation">`</span>liked<span class="token punctuation">`</span> <span class="token keyword">int</span><span class="token punctuation">(</span><span class="token number">8</span><span class="token punctuation">)</span> unsigned <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span> <span class="token keyword">COMMENT</span> <span class="token string">'点赞数'</span><span class="token punctuation">,</span>  <span class="token punctuation">`</span><span class="token keyword">status</span><span class="token punctuation">`</span> <span class="token keyword">tinyint</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span> unsigned <span class="token keyword">DEFAULT</span> <span class="token boolean">NULL</span> <span class="token keyword">COMMENT</span> <span class="token string">'状态，0：正常，1：被举报，2：禁止查看'</span><span class="token punctuation">,</span>  <span class="token punctuation">`</span>create_time<span class="token punctuation">`</span> <span class="token keyword">timestamp</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token keyword">CURRENT_TIMESTAMP</span> <span class="token keyword">COMMENT</span> <span class="token string">'创建时间'</span><span class="token punctuation">,</span>  <span class="token punctuation">`</span>update_time<span class="token punctuation">`</span> <span class="token keyword">timestamp</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token keyword">CURRENT_TIMESTAMP</span> <span class="token keyword">ON</span> <span class="token keyword">UPDATE</span> <span class="token keyword">CURRENT_TIMESTAMP</span> <span class="token keyword">COMMENT</span> <span class="token string">'更新时间'</span><span class="token punctuation">,</span>  <span class="token keyword">PRIMARY</span> <span class="token keyword">KEY</span> <span class="token punctuation">(</span><span class="token punctuation">`</span>id<span class="token punctuation">`</span><span class="token punctuation">)</span> <span class="token keyword">USING</span> <span class="token keyword">BTREE</span><span class="token punctuation">)</span> <span class="token keyword">ENGINE</span><span class="token operator">=</span><span class="token keyword">InnoDB</span> <span class="token keyword">DEFAULT</span> <span class="token keyword">CHARSET</span><span class="token operator">=</span>utf8mb4 ROW_FORMAT<span class="token operator">=</span>COMPACT<span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>具体发布流程</strong></p><p><img src="https://img.jwt1399.top/img/202211282140795.png"></p><p>上传接口</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Slf4j</span><span class="token annotation punctuation">@RestController</span><span class="token annotation punctuation">@RequestMapping</span><span class="token punctuation">(</span><span class="token string">"upload"</span><span class="token punctuation">)</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">UploadController</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@PostMapping</span><span class="token punctuation">(</span><span class="token string">"blog"</span><span class="token punctuation">)</span>    <span class="token keyword">public</span> Result <span class="token function">uploadImage</span><span class="token punctuation">(</span><span class="token annotation punctuation">@RequestParam</span><span class="token punctuation">(</span><span class="token string">"file"</span><span class="token punctuation">)</span> MultipartFile image<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">try</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 获取原始文件名称</span>            String originalFilename <span class="token operator">=</span> image<span class="token punctuation">.</span><span class="token function">getOriginalFilename</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">// 生成新文件名</span>            String fileName <span class="token operator">=</span> <span class="token function">createNewFileName</span><span class="token punctuation">(</span>originalFilename<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">// 保存文件</span>            image<span class="token punctuation">.</span><span class="token function">transferTo</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">File</span><span class="token punctuation">(</span>SystemConstants<span class="token punctuation">.</span>IMAGE_UPLOAD_DIR<span class="token punctuation">,</span> fileName<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">// 返回结果</span>            log<span class="token punctuation">.</span><span class="token function">debug</span><span class="token punctuation">(</span><span class="token string">"文件上传成功，{}"</span><span class="token punctuation">,</span> fileName<span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">ok</span><span class="token punctuation">(</span>fileName<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">IOException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">RuntimeException</span><span class="token punctuation">(</span><span class="token string">"文件上传失败"</span><span class="token punctuation">,</span> e<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">//注意：操作时，需要修改SystemConstants.IMAGE_UPLOAD_DIR 自己图片所在的地址，在实际开发中图片一般会放在nginx上或者是云存储上。</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>发布接口</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@RestController</span><span class="token annotation punctuation">@RequestMapping</span><span class="token punctuation">(</span><span class="token string">"/blog"</span><span class="token punctuation">)</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">BlogController</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Resource</span>    <span class="token keyword">private</span> IBlogService blogService<span class="token punctuation">;</span>    <span class="token annotation punctuation">@PostMapping</span>    <span class="token keyword">public</span> Result <span class="token function">saveBlog</span><span class="token punctuation">(</span><span class="token annotation punctuation">@RequestBody</span> Blog blog<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 获取登录用户</span>        UserDTO user <span class="token operator">=</span> UserHolder<span class="token punctuation">.</span><span class="token function">getUser</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        blog<span class="token punctuation">.</span><span class="token function">setUserId</span><span class="token punctuation">(</span>user<span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 保存探店博文</span>        blogService<span class="token punctuation">.</span><span class="token function">save</span><span class="token punctuation">(</span>blog<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 返回id</span>        <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">ok</span><span class="token punctuation">(</span>blog<span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="❷查看探店笔记"><a href="#❷查看探店笔记" class="headerlink" title="❷查看探店笔记"></a>❷查看探店笔记</h2><p><img src="https://img.jwt1399.top/img/202211282150749.png"></p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@RestController</span><span class="token annotation punctuation">@RequestMapping</span><span class="token punctuation">(</span><span class="token string">"/blog"</span><span class="token punctuation">)</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">BlogController</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Resource</span>    <span class="token keyword">private</span> IBlogService blogService<span class="token punctuation">;</span>      <span class="token annotation punctuation">@GetMapping</span><span class="token punctuation">(</span><span class="token string">"/{id}"</span><span class="token punctuation">)</span>    <span class="token keyword">public</span> Result <span class="token function">queryBlogById</span><span class="token punctuation">(</span><span class="token annotation punctuation">@PathVariable</span><span class="token punctuation">(</span><span class="token string">"id"</span><span class="token punctuation">)</span> Long id<span class="token punctuation">)</span> <span class="token punctuation">{</span>      <span class="token keyword">return</span> blogService<span class="token punctuation">.</span><span class="token function">queryBlogById</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Service</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">BlogServiceImpl</span> <span class="token keyword">extends</span> <span class="token class-name">ServiceImpl</span><span class="token operator">&lt;</span>BlogMapper<span class="token punctuation">,</span> Blog<span class="token operator">></span> <span class="token keyword">implements</span> <span class="token class-name">IBlogService</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Resource</span>    <span class="token keyword">private</span> IUserService userService<span class="token punctuation">;</span>    <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> Result <span class="token function">queryBlogById</span><span class="token punctuation">(</span>Long id<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 1.查询blog</span>        Blog blog <span class="token operator">=</span> <span class="token function">getById</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>blog <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">fail</span><span class="token punctuation">(</span><span class="token string">"笔记不存在！"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 2.查询blog对应用户</span>        <span class="token function">queryBlogUser</span><span class="token punctuation">(</span>blog<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">ok</span><span class="token punctuation">(</span>blog<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">queryBlogUser</span><span class="token punctuation">(</span>Blog blog<span class="token punctuation">)</span> <span class="token punctuation">{</span>        Long userId <span class="token operator">=</span> blog<span class="token punctuation">.</span><span class="token function">getUserId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        User user <span class="token operator">=</span> userService<span class="token punctuation">.</span><span class="token function">getById</span><span class="token punctuation">(</span>userId<span class="token punctuation">)</span><span class="token punctuation">;</span>        blog<span class="token punctuation">.</span><span class="token function">setName</span><span class="token punctuation">(</span>user<span class="token punctuation">.</span><span class="token function">getNickName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        blog<span class="token punctuation">.</span><span class="token function">setIcon</span><span class="token punctuation">(</span>user<span class="token punctuation">.</span><span class="token function">getIcon</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>  <span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="❸点赞功能"><a href="#❸点赞功能" class="headerlink" title="❸点赞功能"></a>❸点赞功能</h2><p>初始代码</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@GetMapping</span><span class="token punctuation">(</span><span class="token string">"/likes/{id}"</span><span class="token punctuation">)</span><span class="token keyword">public</span> Result <span class="token function">queryBlogLikes</span><span class="token punctuation">(</span><span class="token annotation punctuation">@PathVariable</span><span class="token punctuation">(</span><span class="token string">"id"</span><span class="token punctuation">)</span> Long id<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">//修改点赞数量</span>    blogService<span class="token punctuation">.</span><span class="token function">update</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">setSql</span><span class="token punctuation">(</span><span class="token string">"liked = liked + 1 "</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">eq</span><span class="token punctuation">(</span><span class="token string">"id"</span><span class="token punctuation">,</span>id<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">update</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">ok</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>问题分析：这种方式会导致一个用户无限点赞，明显是不合理的</p><p>造成这个问题的原因是，我们现在的逻辑，发起请求只是给数据库+1，所以才会出现这个问题</p><p><img src="/../images/Redis-%E5%AE%9E%E6%88%98%E7%AF%87/1653581590453.png" alt="1653581590453"></p><p>完善点赞功能</p><ul><li>同一个用户只能点赞一次，再次点击则取消点赞</li><li>如果当前用户已经点赞，则点赞按钮高亮显示（前端已实现，判断Blog类的isLike属性）</li></ul><p>实现步骤：</p><ul><li>1.给Blog类中添加一个isLike字段，标示是否被当前用户点赞</li><li>2.修改点赞功能，利用Redis的set集合判断是否点赞过，未点赞过则点赞数+1，并将用户添加到set集合；已点赞过则点赞数-1，并将用户从set集合移除</li><li>3.修改根据id查询Blog的业务，判断当前登录用户是否点赞过，赋值给isLike字段</li><li>4.修改分页查询Blog业务，判断当前登录用户是否点赞过，赋值给isLike字段</li></ul><p>为什么采用set集合：因为用户不能重复点赞，即添加到redis的数据是不能重复的，所以用set集合</p><p>具体步骤：</p><p><strong>步骤一</strong></p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@TableField</span><span class="token punctuation">(</span>exist <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">)</span><span class="token keyword">private</span> Boolean isLike<span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p><strong>步骤二</strong></p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Override</span><span class="token keyword">public</span> Result <span class="token function">likeBlog</span><span class="token punctuation">(</span>Long id<span class="token punctuation">)</span> <span class="token punctuation">{</span>  <span class="token comment" spellcheck="true">// 1.获取登录用户</span>  Long userId <span class="token operator">=</span> UserHolder<span class="token punctuation">.</span><span class="token function">getUser</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment" spellcheck="true">// 2.判断当前登录用户是否已经点赞</span>  String key <span class="token operator">=</span> BLOG_LIKED_KEY <span class="token operator">+</span> id<span class="token punctuation">;</span>  Boolean isMember <span class="token operator">=</span> stringRedisTemplate<span class="token punctuation">.</span><span class="token function">opsForSet</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">isMember</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> userId<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span>BooleanUtil<span class="token punctuation">.</span><span class="token function">isFalse</span><span class="token punctuation">(</span>isMember<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">//3.如果未点赞，可以点赞</span>    <span class="token comment" spellcheck="true">//3.1 数据库点赞数+1</span>    <span class="token keyword">boolean</span> isSuccess <span class="token operator">=</span> <span class="token function">update</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">setSql</span><span class="token punctuation">(</span><span class="token string">"liked = liked + 1"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">eq</span><span class="token punctuation">(</span><span class="token string">"id"</span><span class="token punctuation">,</span> id<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">update</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">//3.2 保存用户到Redis的set集合</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>isSuccess<span class="token punctuation">)</span> <span class="token punctuation">{</span>      stringRedisTemplate<span class="token punctuation">.</span><span class="token function">opsForSet</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> userId<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>  <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">//4.如果已点赞，取消点赞</span>    <span class="token comment" spellcheck="true">//4.1 数据库点赞数-1</span>    <span class="token keyword">boolean</span> isSuccess <span class="token operator">=</span> <span class="token function">update</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">setSql</span><span class="token punctuation">(</span><span class="token string">"liked = liked - 1"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">eq</span><span class="token punctuation">(</span><span class="token string">"id"</span><span class="token punctuation">,</span> id<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">update</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">//4.2 把用户从Redis的set集合移除</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>isSuccess<span class="token punctuation">)</span> <span class="token punctuation">{</span>      stringRedisTemplate<span class="token punctuation">.</span><span class="token function">opsForSet</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> userId<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>  <span class="token punctuation">}</span>  <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">ok</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤三</strong></p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Override</span><span class="token keyword">public</span> Result <span class="token function">queryBlogById</span><span class="token punctuation">(</span>Long id<span class="token punctuation">)</span> <span class="token punctuation">{</span>  <span class="token comment" spellcheck="true">// 1.查询blog</span>  Blog blog <span class="token operator">=</span> <span class="token function">getById</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span>blog <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">fail</span><span class="token punctuation">(</span><span class="token string">"笔记不存在！"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">}</span>  <span class="token comment" spellcheck="true">// 2.查询blog有关的用户</span>  <span class="token function">queryBlogUser</span><span class="token punctuation">(</span>blog<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment" spellcheck="true">// 3.查询blog是否被点赞</span>  <span class="token function">isBlogLiked</span><span class="token punctuation">(</span>blog<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">ok</span><span class="token punctuation">(</span>blog<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">isBlogLiked</span><span class="token punctuation">(</span>Blog blog<span class="token punctuation">)</span> <span class="token punctuation">{</span>  <span class="token comment" spellcheck="true">// 1.获取登录用户</span>  UserDTO user <span class="token operator">=</span> UserHolder<span class="token punctuation">.</span><span class="token function">getUser</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment" spellcheck="true">//用户未登录，无需查询是否点赞</span>  <span class="token keyword">if</span><span class="token punctuation">(</span>user <span class="token operator">==</span> null<span class="token punctuation">)</span><span class="token punctuation">{</span>    <span class="token keyword">return</span><span class="token punctuation">;</span>  <span class="token punctuation">}</span>  Long userId <span class="token operator">=</span> user<span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment" spellcheck="true">// 2.判断当前登录用户是否已经点赞</span>  String key <span class="token operator">=</span> BLOG_LIKED_KEY <span class="token operator">+</span> blog<span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  Boolean isMember <span class="token operator">=</span> stringRedisTemplate<span class="token punctuation">.</span><span class="token function">opsForSet</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">isMember</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> userId<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  blog<span class="token punctuation">.</span><span class="token function">setIsLike</span><span class="token punctuation">(</span>BooleanUtil<span class="token punctuation">.</span><span class="token function">isTrue</span><span class="token punctuation">(</span>isMember<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>步骤四</strong></p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Override</span><span class="token keyword">public</span> Result <span class="token function">queryHotBlog</span><span class="token punctuation">(</span>Integer current<span class="token punctuation">)</span> <span class="token punctuation">{</span>  <span class="token comment" spellcheck="true">// 根据用户查询</span>  Page<span class="token operator">&lt;</span>Blog<span class="token operator">></span> page <span class="token operator">=</span> <span class="token function">query</span><span class="token punctuation">(</span><span class="token punctuation">)</span>    <span class="token punctuation">.</span><span class="token function">orderByDesc</span><span class="token punctuation">(</span><span class="token string">"liked"</span><span class="token punctuation">)</span>    <span class="token punctuation">.</span><span class="token function">page</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Page</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span>current<span class="token punctuation">,</span> SystemConstants<span class="token punctuation">.</span>MAX_PAGE_SIZE<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment" spellcheck="true">// 获取当前页数据</span>  List<span class="token operator">&lt;</span>Blog<span class="token operator">></span> records <span class="token operator">=</span> page<span class="token punctuation">.</span><span class="token function">getRecords</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment" spellcheck="true">// 查询用户</span>  records<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span>blog <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span>    <span class="token function">queryBlogUser</span><span class="token punctuation">(</span>blog<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token function">isBlogLiked</span><span class="token punctuation">(</span>blog<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//判读是否被点赞</span>  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">ok</span><span class="token punctuation">(</span>records<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="❹点赞排行榜"><a href="#❹点赞排行榜" class="headerlink" title="❹点赞排行榜"></a>❹点赞排行榜</h2><p>在探店笔记的详情页面，应该把给该笔记点赞的人显示出来，比如最早点赞的TOP5，形成点赞排行榜：</p><p>之前点赞的用户是放到set集合，但是set集合是不能排序的，因此可以采用一个可以排序的set集合<code>sortedSet</code></p><p><img src="https://img.jwt1399.top/img/202211292324898.png"></p><p><strong>为什么使用sortedSet？</strong></p><p>所有点赞的人，需要是唯一的，所以我们应当使用set或者是sortedSet，其次我们需要排序，就可以直接锁定使用sortedSet啦</p><p><img src="https://img.jwt1399.top/img/202211292329759.png"></p><ul><li>修改点赞逻辑代码，将set改为sortedSet</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Override</span><span class="token keyword">public</span> Result <span class="token function">likeBlog</span><span class="token punctuation">(</span>Long id<span class="token punctuation">)</span> <span class="token punctuation">{</span>  <span class="token comment" spellcheck="true">// 1.获取登录用户</span>  Long userId <span class="token operator">=</span> UserHolder<span class="token punctuation">.</span><span class="token function">getUser</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment" spellcheck="true">// 2.判断当前登录用户是否已经点赞</span>  String key <span class="token operator">=</span> BLOG_LIKED_KEY <span class="token operator">+</span> id<span class="token punctuation">;</span>  Double score <span class="token operator">=</span> stringRedisTemplate<span class="token punctuation">.</span><span class="token function">opsForZSet</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">score</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> userId<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span>score <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 3.如果未点赞，可以点赞</span>    <span class="token comment" spellcheck="true">// 3.1.数据库点赞数 + 1</span>    <span class="token keyword">boolean</span> isSuccess <span class="token operator">=</span> <span class="token function">update</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">setSql</span><span class="token punctuation">(</span><span class="token string">"liked = liked + 1"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">eq</span><span class="token punctuation">(</span><span class="token string">"id"</span><span class="token punctuation">,</span> id<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">update</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 3.2.保存用户到Redis的set集合  zadd key value score</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>isSuccess<span class="token punctuation">)</span> <span class="token punctuation">{</span>      stringRedisTemplate<span class="token punctuation">.</span><span class="token function">opsForZSet</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> userId<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> System<span class="token punctuation">.</span><span class="token function">currentTimeMillis</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>  <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 4.如果已点赞，取消点赞</span>    <span class="token comment" spellcheck="true">// 4.1.数据库点赞数 -1</span>    <span class="token keyword">boolean</span> isSuccess <span class="token operator">=</span> <span class="token function">update</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">setSql</span><span class="token punctuation">(</span><span class="token string">"liked = liked - 1"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">eq</span><span class="token punctuation">(</span><span class="token string">"id"</span><span class="token punctuation">,</span> id<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">update</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 4.2.把用户从Redis的set集合移除</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>isSuccess<span class="token punctuation">)</span> <span class="token punctuation">{</span>      stringRedisTemplate<span class="token punctuation">.</span><span class="token function">opsForZSet</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> userId<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>  <span class="token punctuation">}</span>  <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">ok</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">isBlogLiked</span><span class="token punctuation">(</span>Blog blog<span class="token punctuation">)</span> <span class="token punctuation">{</span>  <span class="token comment" spellcheck="true">// 1.获取登录用户</span>  UserDTO user <span class="token operator">=</span> UserHolder<span class="token punctuation">.</span><span class="token function">getUser</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span>user <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 用户未登录，无需查询是否点赞</span>    <span class="token keyword">return</span><span class="token punctuation">;</span>  <span class="token punctuation">}</span>  Long userId <span class="token operator">=</span> user<span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment" spellcheck="true">// 2.判断当前登录用户是否已经点赞</span>  String key <span class="token operator">=</span> <span class="token string">"blog:liked:"</span> <span class="token operator">+</span> blog<span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  Double score <span class="token operator">=</span> stringRedisTemplate<span class="token punctuation">.</span><span class="token function">opsForZSet</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">score</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> userId<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  blog<span class="token punctuation">.</span><span class="token function">setIsLike</span><span class="token punctuation">(</span>score <span class="token operator">!=</span> null<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>点赞排行榜显示</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// BlogController</span><span class="token annotation punctuation">@GetMapping</span><span class="token punctuation">(</span><span class="token string">"/likes/{id}"</span><span class="token punctuation">)</span><span class="token keyword">public</span> Result <span class="token function">queryBlogLikes</span><span class="token punctuation">(</span><span class="token annotation punctuation">@PathVariable</span><span class="token punctuation">(</span><span class="token string">"id"</span><span class="token punctuation">)</span> Long id<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">return</span> blogService<span class="token punctuation">.</span><span class="token function">queryBlogLikes</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// BlogService</span><span class="token annotation punctuation">@Override</span><span class="token keyword">public</span> Result <span class="token function">queryBlogLikes</span><span class="token punctuation">(</span>Long id<span class="token punctuation">)</span> <span class="token punctuation">{</span>    String key <span class="token operator">=</span> BLOG_LIKED_KEY <span class="token operator">+</span> id<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 1.查询top5的点赞用户 zrange key 0 4</span>    Set<span class="token operator">&lt;</span>String<span class="token operator">></span> top5 <span class="token operator">=</span> stringRedisTemplate<span class="token punctuation">.</span><span class="token function">opsForZSet</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">range</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">4</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>top5 <span class="token operator">==</span> null <span class="token operator">||</span> top5<span class="token punctuation">.</span><span class="token function">isEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">ok</span><span class="token punctuation">(</span>Collections<span class="token punctuation">.</span><span class="token function">emptyList</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">// 2.解析出其中的用户id</span>    List<span class="token operator">&lt;</span>Long<span class="token operator">></span> ids <span class="token operator">=</span> top5<span class="token punctuation">.</span><span class="token function">stream</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span>Long<span class="token operator">:</span><span class="token operator">:</span>valueOf<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">collect</span><span class="token punctuation">(</span>Collectors<span class="token punctuation">.</span><span class="token function">toList</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    String idStr <span class="token operator">=</span> StrUtil<span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span><span class="token string">","</span><span class="token punctuation">,</span> ids<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 3.根据用户id查询用户 WHERE id IN ( 5 , 1 ) ORDER BY FIELD(id, 5, 1)</span>    List<span class="token operator">&lt;</span>UserDTO<span class="token operator">></span> userDTOS <span class="token operator">=</span> userService<span class="token punctuation">.</span><span class="token function">query</span><span class="token punctuation">(</span><span class="token punctuation">)</span>            <span class="token punctuation">.</span><span class="token function">in</span><span class="token punctuation">(</span><span class="token string">"id"</span><span class="token punctuation">,</span> ids<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">last</span><span class="token punctuation">(</span><span class="token string">"ORDER BY FIELD(id,"</span> <span class="token operator">+</span> idStr <span class="token operator">+</span> <span class="token string">")"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">list</span><span class="token punctuation">(</span><span class="token punctuation">)</span>            <span class="token punctuation">.</span><span class="token function">stream</span><span class="token punctuation">(</span><span class="token punctuation">)</span>            <span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span>user <span class="token operator">-</span><span class="token operator">></span> BeanUtil<span class="token punctuation">.</span><span class="token function">copyProperties</span><span class="token punctuation">(</span>user<span class="token punctuation">,</span> UserDTO<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">)</span>            <span class="token punctuation">.</span><span class="token function">collect</span><span class="token punctuation">(</span>Collectors<span class="token punctuation">.</span><span class="token function">toList</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 4.返回</span>    <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">ok</span><span class="token punctuation">(</span>userDTOS<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h1 id="⑨好友关注-Set"><a href="#⑨好友关注-Set" class="headerlink" title="⑨好友关注-Set"></a>⑨好友关注-Set</h1><h2 id="❶关注和取关"><a href="#❶关注和取关" class="headerlink" title="❶关注和取关"></a>❶关注和取关</h2><p><img src="https://img.jwt1399.top/img/202211301608038.png"></p><p>实现思路：基于该表数据结构，实现两个接口：</p><ul><li>关注和取关接口</li><li>判断是否关注的接口</li></ul><p>关注是User之间的关系，是博主与粉丝的关系，数据库中有一张tb_follow表来标示：</p><pre class="line-numbers language-sql"><code class="language-sql"><span class="token keyword">CREATE</span> <span class="token keyword">TABLE</span> <span class="token punctuation">`</span>tb_follow<span class="token punctuation">`</span> <span class="token punctuation">(</span>  <span class="token punctuation">`</span>id<span class="token punctuation">`</span> <span class="token keyword">bigint</span><span class="token punctuation">(</span><span class="token number">20</span><span class="token punctuation">)</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">AUTO_INCREMENT</span> <span class="token keyword">COMMENT</span> <span class="token string">'主键'</span><span class="token punctuation">,</span>  <span class="token punctuation">`</span>user_id<span class="token punctuation">`</span> <span class="token keyword">bigint</span><span class="token punctuation">(</span><span class="token number">20</span><span class="token punctuation">)</span> unsigned <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">COMMENT</span> <span class="token string">'用户id'</span><span class="token punctuation">,</span>  <span class="token punctuation">`</span>follow_user_id<span class="token punctuation">`</span> <span class="token keyword">bigint</span><span class="token punctuation">(</span><span class="token number">20</span><span class="token punctuation">)</span> unsigned <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">COMMENT</span> <span class="token string">'关注的用户id'</span><span class="token punctuation">,</span>  <span class="token punctuation">`</span>create_time<span class="token punctuation">`</span> <span class="token keyword">timestamp</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token keyword">CURRENT_TIMESTAMP</span> <span class="token keyword">COMMENT</span> <span class="token string">'创建时间'</span><span class="token punctuation">,</span>  <span class="token keyword">PRIMARY</span> <span class="token keyword">KEY</span> <span class="token punctuation">(</span><span class="token punctuation">`</span>id<span class="token punctuation">`</span><span class="token punctuation">)</span> <span class="token keyword">USING</span> <span class="token keyword">BTREE</span><span class="token punctuation">)</span> <span class="token keyword">ENGINE</span><span class="token operator">=</span><span class="token keyword">InnoDB</span> <span class="token keyword">DEFAULT</span> <span class="token keyword">CHARSET</span><span class="token operator">=</span>utf8mb4 ROW_FORMAT<span class="token operator">=</span>COMPACT<span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>FollowController</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Resource</span><span class="token keyword">private</span> IFollowService followService<span class="token punctuation">;</span><span class="token comment" spellcheck="true">//关注&amp;取关</span><span class="token annotation punctuation">@PutMapping</span><span class="token punctuation">(</span><span class="token string">"/{id}/{isFollow}"</span><span class="token punctuation">)</span><span class="token keyword">public</span> Result <span class="token function">follow</span><span class="token punctuation">(</span><span class="token annotation punctuation">@PathVariable</span><span class="token punctuation">(</span><span class="token string">"id"</span><span class="token punctuation">)</span> Long followUserId<span class="token punctuation">,</span> <span class="token annotation punctuation">@PathVariable</span><span class="token punctuation">(</span><span class="token string">"isFollow"</span><span class="token punctuation">)</span> Boolean isFollow<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token keyword">return</span> followService<span class="token punctuation">.</span><span class="token function">follow</span><span class="token punctuation">(</span>followUserId<span class="token punctuation">,</span> isFollow<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">//判断是否关注</span><span class="token annotation punctuation">@GetMapping</span><span class="token punctuation">(</span><span class="token string">"/or/not/{id}"</span><span class="token punctuation">)</span><span class="token keyword">public</span> Result <span class="token function">isFollow</span><span class="token punctuation">(</span><span class="token annotation punctuation">@PathVariable</span><span class="token punctuation">(</span><span class="token string">"id"</span><span class="token punctuation">)</span> Long followUserId<span class="token punctuation">)</span> <span class="token punctuation">{</span>      <span class="token keyword">return</span> followService<span class="token punctuation">.</span><span class="token function">isFollow</span><span class="token punctuation">(</span>followUserId<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>FollowService</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">//关注&amp;取关</span><span class="token annotation punctuation">@Override</span><span class="token keyword">public</span> Result <span class="token function">follow</span><span class="token punctuation">(</span>Long followUserId<span class="token punctuation">,</span> Boolean isFollow<span class="token punctuation">)</span> <span class="token punctuation">{</span>  <span class="token comment" spellcheck="true">// 1.获取登录用户</span>  Long userId <span class="token operator">=</span> UserHolder<span class="token punctuation">.</span><span class="token function">getUser</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  String key <span class="token operator">=</span> <span class="token string">"follows:"</span> <span class="token operator">+</span> userId<span class="token punctuation">;</span>  <span class="token comment" spellcheck="true">// 1.判断到底是关注还是取关</span>  <span class="token keyword">if</span> <span class="token punctuation">(</span>isFollow<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 2.关注，新增数据</span>    Follow follow <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Follow</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    follow<span class="token punctuation">.</span><span class="token function">setUserId</span><span class="token punctuation">(</span>userId<span class="token punctuation">)</span><span class="token punctuation">;</span>    follow<span class="token punctuation">.</span><span class="token function">setFollowUserId</span><span class="token punctuation">(</span>followUserId<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">boolean</span> isSuccess <span class="token operator">=</span> <span class="token function">save</span><span class="token punctuation">(</span>follow<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 3.取关，删除 delete from tb_follow where user_id = ? and follow_user_id = ?</span>    <span class="token function">remove</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">QueryWrapper</span><span class="token operator">&lt;</span>Follow<span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span>           <span class="token punctuation">.</span><span class="token function">eq</span><span class="token punctuation">(</span><span class="token string">"user_id"</span><span class="token punctuation">,</span> userId<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">eq</span><span class="token punctuation">(</span><span class="token string">"follow_user_id"</span><span class="token punctuation">,</span> followUserId<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">}</span>  <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">ok</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">//判断是否关注</span><span class="token annotation punctuation">@Override</span><span class="token keyword">public</span> Result <span class="token function">isFollow</span><span class="token punctuation">(</span>Long followUserId<span class="token punctuation">)</span> <span class="token punctuation">{</span>  <span class="token comment" spellcheck="true">// 1.获取登录用户</span>  Long userId <span class="token operator">=</span> UserHolder<span class="token punctuation">.</span><span class="token function">getUser</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment" spellcheck="true">// 2.查询是否关注 select count(*) from tb_follow where user_id = ? and follow_user_id = ?</span>  Integer count <span class="token operator">=</span> <span class="token function">query</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">eq</span><span class="token punctuation">(</span><span class="token string">"user_id"</span><span class="token punctuation">,</span> userId<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">eq</span><span class="token punctuation">(</span><span class="token string">"follow_user_id"</span><span class="token punctuation">,</span> followUserId<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">count</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment" spellcheck="true">// 3.判断</span>  <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">ok</span><span class="token punctuation">(</span>count <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="❷共同关注"><a href="#❷共同关注" class="headerlink" title="❷共同关注"></a>❷共同关注</h2><p>想要查看共同关注的好友，需要首先进入到这个页面，这个页面会发起两个请求</p><ul><li><p>1、去查询用户的详情</p></li><li><p>2、去查询用户的笔记</p></li></ul><p><img src="https://img.jwt1399.top/img/202212011715597.png"></p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">// UserController 根据id查询用户</span><span class="token annotation punctuation">@GetMapping</span><span class="token punctuation">(</span><span class="token string">"/{id}"</span><span class="token punctuation">)</span><span class="token keyword">public</span> Result <span class="token function">queryUserById</span><span class="token punctuation">(</span><span class="token annotation punctuation">@PathVariable</span><span class="token punctuation">(</span><span class="token string">"id"</span><span class="token punctuation">)</span> Long userId<span class="token punctuation">)</span><span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 查询详情</span>    User user <span class="token operator">=</span> userService<span class="token punctuation">.</span><span class="token function">getById</span><span class="token punctuation">(</span>userId<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>user <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">ok</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    UserDTO userDTO <span class="token operator">=</span> BeanUtil<span class="token punctuation">.</span><span class="token function">copyProperties</span><span class="token punctuation">(</span>user<span class="token punctuation">,</span> UserDTO<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 返回</span>    <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">ok</span><span class="token punctuation">(</span>userDTO<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">// BlogController  根据id查询博主的探店笔记</span><span class="token annotation punctuation">@GetMapping</span><span class="token punctuation">(</span><span class="token string">"/of/user"</span><span class="token punctuation">)</span><span class="token keyword">public</span> Result <span class="token function">queryBlogByUserId</span><span class="token punctuation">(</span>        <span class="token annotation punctuation">@RequestParam</span><span class="token punctuation">(</span>value <span class="token operator">=</span> <span class="token string">"current"</span><span class="token punctuation">,</span> defaultValue <span class="token operator">=</span> <span class="token string">"1"</span><span class="token punctuation">)</span> Integer current<span class="token punctuation">,</span>        <span class="token annotation punctuation">@RequestParam</span><span class="token punctuation">(</span><span class="token string">"id"</span><span class="token punctuation">)</span> Long id<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 根据用户查询</span>    Page<span class="token operator">&lt;</span>Blog<span class="token operator">></span> page <span class="token operator">=</span> blogService<span class="token punctuation">.</span><span class="token function">query</span><span class="token punctuation">(</span><span class="token punctuation">)</span>            <span class="token punctuation">.</span><span class="token function">eq</span><span class="token punctuation">(</span><span class="token string">"user_id"</span><span class="token punctuation">,</span> id<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">page</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Page</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span>current<span class="token punctuation">,</span> SystemConstants<span class="token punctuation">.</span>MAX_PAGE_SIZE<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 获取当前页数据</span>    List<span class="token operator">&lt;</span>Blog<span class="token operator">></span> records <span class="token operator">=</span> page<span class="token punctuation">.</span><span class="token function">getRecords</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">ok</span><span class="token punctuation">(</span>records<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>接下来我们来看看共同关注如何实现：</p><p>需求：利用Redis中恰当的数据结构，实现共同关注功能。在博主个人页面展示出当前用户与博主的共同关注。</p><p>在set集合中，有交集并集补集的api，我们可以把两人的关注的人分别放入到一个set集合中，然后再通过api去查看这两个set集合中的交集数据。</p><p><img src="https://img.jwt1399.top/img/202212011726677.png"></p><p>我们先来改造当前的关注列表</p><p>改造原因是因为我们需要在用户关注了某位用户后，需要将数据放入到set集合中，方便后续进行共同关注，同时当取消关注时，也需要从set集合中进行删除</p><ul><li>FollowServiceImpl</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Override</span><span class="token keyword">public</span> Result <span class="token function">follow</span><span class="token punctuation">(</span>Long followUserId<span class="token punctuation">,</span> Boolean isFollow<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 1.获取登录用户</span>    Long userId <span class="token operator">=</span> UserHolder<span class="token punctuation">.</span><span class="token function">getUser</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    String key <span class="token operator">=</span> <span class="token string">"follows:"</span> <span class="token operator">+</span> userId<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 1.判断到底是关注还是取关</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>isFollow<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 2.关注，新增数据</span>        Follow follow <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Follow</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        follow<span class="token punctuation">.</span><span class="token function">setUserId</span><span class="token punctuation">(</span>userId<span class="token punctuation">)</span><span class="token punctuation">;</span>        follow<span class="token punctuation">.</span><span class="token function">setFollowUserId</span><span class="token punctuation">(</span>followUserId<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">boolean</span> isSuccess <span class="token operator">=</span> <span class="token function">save</span><span class="token punctuation">(</span>follow<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>isSuccess<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 把关注用户的id，放入redis的set集合 sadd userId followerUserId</span>            stringRedisTemplate<span class="token punctuation">.</span><span class="token function">opsForSet</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> followUserId<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 3.取关，删除 delete from tb_follow where user_id = ? and follow_user_id = ?</span>        <span class="token keyword">boolean</span> isSuccess <span class="token operator">=</span> <span class="token function">remove</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">QueryWrapper</span><span class="token operator">&lt;</span>Follow<span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span>                <span class="token punctuation">.</span><span class="token function">eq</span><span class="token punctuation">(</span><span class="token string">"user_id"</span><span class="token punctuation">,</span> userId<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">eq</span><span class="token punctuation">(</span><span class="token string">"follow_user_id"</span><span class="token punctuation">,</span> followUserId<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>isSuccess<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 把关注用户的id从Redis集合中移除</span>            stringRedisTemplate<span class="token punctuation">.</span><span class="token function">opsForSet</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> followUserId<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span>    <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">ok</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>共同关注接口：</strong></p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@GetMapping</span><span class="token punctuation">(</span><span class="token string">"/common/{id}"</span><span class="token punctuation">)</span><span class="token keyword">public</span> Result <span class="token function">followCommons</span><span class="token punctuation">(</span><span class="token annotation punctuation">@PathVariable</span><span class="token punctuation">(</span><span class="token string">"id"</span><span class="token punctuation">)</span> Long id<span class="token punctuation">)</span><span class="token punctuation">{</span>  <span class="token keyword">return</span> followService<span class="token punctuation">.</span><span class="token function">followcommon</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Override</span><span class="token keyword">public</span> Result <span class="token function">followCommons</span><span class="token punctuation">(</span>Long id<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 1.获取当前用户</span>    Long userId <span class="token operator">=</span> UserHolder<span class="token punctuation">.</span><span class="token function">getUser</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    String key <span class="token operator">=</span> <span class="token string">"follows:"</span> <span class="token operator">+</span> userId<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 2.求交集</span>    String key2 <span class="token operator">=</span> <span class="token string">"follows:"</span> <span class="token operator">+</span> id<span class="token punctuation">;</span>    Set<span class="token operator">&lt;</span>String<span class="token operator">></span> intersect <span class="token operator">=</span> stringRedisTemplate<span class="token punctuation">.</span><span class="token function">opsForSet</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">intersect</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> key2<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>intersect <span class="token operator">==</span> null <span class="token operator">||</span> intersect<span class="token punctuation">.</span><span class="token function">isEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 无交集</span>        <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">ok</span><span class="token punctuation">(</span>Collections<span class="token punctuation">.</span><span class="token function">emptyList</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">// 3.解析id集合</span>    List<span class="token operator">&lt;</span>Long<span class="token operator">></span> ids <span class="token operator">=</span> intersect<span class="token punctuation">.</span><span class="token function">stream</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span>Long<span class="token operator">:</span><span class="token operator">:</span>valueOf<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">collect</span><span class="token punctuation">(</span>Collectors<span class="token punctuation">.</span><span class="token function">toList</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 4.查询用户</span>    List<span class="token operator">&lt;</span>UserDTO<span class="token operator">></span> users <span class="token operator">=</span> userService<span class="token punctuation">.</span><span class="token function">listByIds</span><span class="token punctuation">(</span>ids<span class="token punctuation">)</span>            <span class="token punctuation">.</span><span class="token function">stream</span><span class="token punctuation">(</span><span class="token punctuation">)</span>            <span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span>user <span class="token operator">-</span><span class="token operator">></span> BeanUtil<span class="token punctuation">.</span><span class="token function">copyProperties</span><span class="token punctuation">(</span>user<span class="token punctuation">,</span> UserDTO<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">)</span>            <span class="token punctuation">.</span><span class="token function">collect</span><span class="token punctuation">(</span>Collectors<span class="token punctuation">.</span><span class="token function">toList</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">ok</span><span class="token punctuation">(</span>users<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="❸关注推送"><a href="#❸关注推送" class="headerlink" title="❸关注推送"></a>❸关注推送</h2><h3 id="1-Feed流实现方案"><a href="#1-Feed流实现方案" class="headerlink" title="1.Feed流实现方案"></a>1.Feed流实现方案</h3><p>当我们关注了用户后，这个用户发了动态，那么我们应该把这些数据推送给用户，这个需求就是关注推送，又叫做Feed流，直译为投喂。为用户持续的提供“沉浸式”的体验，通过无限下拉刷新获取新的信息。</p><p>对于传统的模式的内容解锁：我们是需要用户去通过搜索引擎或者是其他的方式去解锁想要看的内容</p><p><img src="https://img.jwt1399.top/img/202212011752007.png"></p><p>对于新型的Feed流的的效果：不需要我们用户再去推送信息，而是系统分析用户到底想要什么，然后直接把内容推送给用户，从而使用户能够更加的节约时间，不用主动去寻找。</p><p><img src="https://img.jwt1399.top/img/202212011752526.png"></p><p>Feed流的实现有两种模式：</p><ul><li><p>Timeline：不做内容筛选，简单的按照内容发布时间排序，常用于好友或关注。例如朋友圈</p><ul><li><p>优点：信息全面，不会有缺失。并且实现也相对简单</p></li><li><p>缺点：信息噪音较多，用户不一定感兴趣，内容获取效率低</p></li></ul></li><li><p>智能排序：利用智能算法屏蔽掉违规的、用户不感兴趣的内容。推送用户感兴趣信息来吸引用户</p><ul><li><p>优点：投喂用户感兴趣信息，用户粘度很高，容易沉迷</p></li><li><p>缺点：如果算法不精准，可能起到反作用</p></li></ul></li></ul><p>本例中的个人页面，是基于关注的好友来做Feed流，因此采用Timeline的模式。该模式的实现方案有三种：</p><ul><li>拉模式</li><li>推模式</li><li>推拉结合</li></ul><p><strong>拉模式</strong>：也叫做读扩散</p><p>该模式的核心含义就是：当张三和李四和王五发了消息后，都会保存在自己的邮箱中，假设赵六要读取信息，那么他会从读取他自己的收件箱，此时系统会从他关注的人群中，把他关注人的信息全部都进行拉取，然后在进行排序</p><ul><li><p>优点：比较节约空间，因为赵六在读信息时，并没有重复读取，而且读取完之后可以把他的收件箱进行清楚。</p></li><li><p>缺点：比较延迟，当用户读取数据时才去关注的人里边去读取数据，假设用户关注了大量的用户，那么此时就会拉取海量的内容，对服务器压力巨大。</p></li></ul><p><img src="https://img.jwt1399.top/img/202212011754963.png"></p><p><strong>推模式</strong>：也叫做写扩散。</p><p>推模式是没有写邮箱的，当张三写了一个内容，此时会主动的把张三写的内容发送到他的粉丝收件箱中去，假设此时李四再来读取，就不用再去临时拉取了</p><ul><li><p>优点：时效快，不用临时拉取</p></li><li><p>缺点：内存压力大，假设一个大V写信息，很多人关注他， 就会写很多分数据到粉丝那边去</p></li></ul><p><img src="https://img.jwt1399.top/img/202212011754945.png"></p><p><strong>推拉结合模式</strong>：也叫做读写混合，兼具推和拉两种模式的优点。</p><p>推拉模式是一个折中的方案，在发件人这一端，如果是个普通的人，那么我们采用推模式，直接把数据写入到他的粉丝中去，因为普通的人他的粉丝关注量比较小，所以这样做没有压力，如果是大V，那么直接将数据先写入到一份到发件箱里边去，然后再直接写一份到活跃粉丝收件箱里边去，现在站在收件人这端来看，如果是活跃粉丝，那么大V和普通的人发的都会直接写入到自己收件箱里边来，而如果是普通的粉丝，由于他们上线不是很频繁，所以等他们上线时，再从发件箱里边去拉信息。</p><p><img src="https://img.jwt1399.top/img/202212011757395.png"></p><h3 id="2-推送到粉丝收件箱"><a href="#2-推送到粉丝收件箱" class="headerlink" title="2.推送到粉丝收件箱"></a>2.推送到粉丝收件箱</h3><p>需求：我们在保存完探店笔记后，然后把数据推送到粉丝的redis中去。</p><ul><li>修改新增探店笔记的业务，在保存blog到数据库的同时，推送到粉丝的收件箱</li><li>收件箱满足可以根据时间戳排序，必须用Redis的数据结构实现</li><li>查询收件箱数据时，可以实现分页查询</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@PostMapping</span><span class="token keyword">public</span> Result <span class="token function">saveBlog</span><span class="token punctuation">(</span><span class="token annotation punctuation">@RequestBody</span> Blog blog<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">return</span> blogService<span class="token punctuation">.</span><span class="token function">saveBlog</span><span class="token punctuation">(</span>blog<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Override</span><span class="token keyword">public</span> Result <span class="token function">saveBlog</span><span class="token punctuation">(</span>Blog blog<span class="token punctuation">)</span> <span class="token punctuation">{</span>  <span class="token comment" spellcheck="true">// 1.获取登录用户</span>  UserDTO user <span class="token operator">=</span> UserHolder<span class="token punctuation">.</span><span class="token function">getUser</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  blog<span class="token punctuation">.</span><span class="token function">setUserId</span><span class="token punctuation">(</span>user<span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment" spellcheck="true">// 2.保存探店笔记</span>  <span class="token keyword">boolean</span> isSuccess <span class="token operator">=</span> <span class="token function">save</span><span class="token punctuation">(</span>blog<span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token keyword">if</span><span class="token punctuation">(</span><span class="token operator">!</span>isSuccess<span class="token punctuation">)</span><span class="token punctuation">{</span>    <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">fail</span><span class="token punctuation">(</span><span class="token string">"新增笔记失败!"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">}</span>  <span class="token comment" spellcheck="true">// 3.查询笔记作者的所有粉丝 select * from tb_follow where follow_user_id = ?</span>  List<span class="token operator">&lt;</span>Follow<span class="token operator">></span> follows <span class="token operator">=</span> followService<span class="token punctuation">.</span><span class="token function">query</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">eq</span><span class="token punctuation">(</span><span class="token string">"follow_user_id"</span><span class="token punctuation">,</span> user<span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">list</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token comment" spellcheck="true">// 4.推送笔记id给所有粉丝</span>  <span class="token keyword">for</span> <span class="token punctuation">(</span>Follow follow <span class="token operator">:</span> follows<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 4.1.获取粉丝id</span>    Long userId <span class="token operator">=</span> follow<span class="token punctuation">.</span><span class="token function">getUserId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 4.2.推送</span>    String key <span class="token operator">=</span> FEED_KEY <span class="token operator">+</span> userId<span class="token punctuation">;</span>    stringRedisTemplate<span class="token punctuation">.</span><span class="token function">opsForZSet</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> blog<span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> System<span class="token punctuation">.</span><span class="token function">currentTimeMillis</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">}</span>  <span class="token comment" spellcheck="true">// 5.返回id</span>  <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">ok</span><span class="token punctuation">(</span>blog<span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="3-实现分页查询收邮箱"><a href="#3-实现分页查询收邮箱" class="headerlink" title="3.实现分页查询收邮箱"></a>3.实现分页查询收邮箱</h3><blockquote><p>需求：在个人主页的“关注”卡片中，查询并展示推送的Blog信息</p></blockquote><p><strong>传统分页</strong>在feed流是不适用的，因为我们的数据会随时发生变化</p><ul><li><p>假设在t1时刻，我们去读取第一页，此时page &#x3D; 1 ，size &#x3D; 5 ，那么我们拿到的就是10~6 这几条记录，</p></li><li><p>假设在t2时刻又发布了一条记录，此时t3 时刻，我们来读取第二页，读取第二页传入的参数是page&#x3D;2 ，size&#x3D;5 那么此时读取到的第二页实际上是从6 开始，然后是6~2 ，那么我们就读取到了重复的数据，所以feed流的分页，不能采用原始方案来做。</p></li></ul><p><img src="https://img.jwt1399.top/img/202212021653849.png"></p><p><strong>Redis情况模拟：</strong></p><pre class="line-numbers language-sql"><code class="language-sql"><span class="token comment" spellcheck="true"># 通过角标倒序查询</span>ZREVRANGE <span class="token keyword">key</span> <span class="token keyword">start</span> stop <span class="token punctuation">[</span>WITHSCORES<span class="token punctuation">]</span><span class="token comment" spellcheck="true"># 通过角标正序查询</span>ZRANGE <span class="token keyword">key</span> <span class="token keyword">start</span> stop <span class="token punctuation">[</span>WITHSCORES<span class="token punctuation">]</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-sql"><code class="language-sql"><span class="token comment" spellcheck="true"># 在Zset中添加6个数据</span><span class="token number">127.0</span><span class="token punctuation">.</span><span class="token number">0.1</span>:<span class="token number">6379</span><span class="token operator">></span>ZADD <span class="token string">"zkey"</span> <span class="token number">6</span> <span class="token string">"m6"</span> <span class="token number">5</span> <span class="token string">"m5"</span> <span class="token number">4</span> <span class="token string">"m4"</span> <span class="token number">3</span> <span class="token string">"m3"</span> <span class="token number">2</span> <span class="token string">"m2"</span> <span class="token number">1</span> <span class="token string">"m1"</span><span class="token comment" spellcheck="true"># 降序查前3个元素</span><span class="token number">127.0</span><span class="token punctuation">.</span><span class="token number">0.1</span>:<span class="token number">6379</span><span class="token operator">></span> ZREVRANGE zkey <span class="token number">0</span> <span class="token number">2</span> WITHSCORES<span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"m6"</span><span class="token number">2</span><span class="token punctuation">)</span> <span class="token string">"6"</span><span class="token number">3</span><span class="token punctuation">)</span> <span class="token string">"m5"</span><span class="token number">4</span><span class="token punctuation">)</span> <span class="token string">"5"</span><span class="token number">5</span><span class="token punctuation">)</span> <span class="token string">"m4"</span><span class="token number">6</span><span class="token punctuation">)</span> <span class="token string">"4"</span><span class="token comment" spellcheck="true"># 添加一个元素</span><span class="token number">127.0</span><span class="token punctuation">.</span><span class="token number">0.1</span>:<span class="token number">6379</span><span class="token operator">></span> ZADD zkey <span class="token number">7</span> m7<span class="token punctuation">(</span><span class="token keyword">integer</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token comment" spellcheck="true"># 降序查后3个元素伴随着分数，查到了重复数据</span><span class="token number">127.0</span><span class="token punctuation">.</span><span class="token number">0.1</span>:<span class="token number">6379</span><span class="token operator">></span> ZREVRANGE zkey <span class="token number">3</span> <span class="token number">5</span> WITHSCORES<span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"m4"</span><span class="token number">2</span><span class="token punctuation">)</span> <span class="token string">"4"</span><span class="token number">3</span><span class="token punctuation">)</span> <span class="token string">"m3"</span><span class="token number">4</span><span class="token punctuation">)</span> <span class="token string">"3"</span><span class="token number">5</span><span class="token punctuation">)</span> <span class="token string">"m2"</span><span class="token number">6</span><span class="token punctuation">)</span> <span class="token string">"2"</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>滚动分页</strong>：我们需要记录每次操作的最后一条，然后从这个位置开始去读取数据</p><ul><li>假设在t1时刻开始，拿第一页数据，拿到了10~6，然后记录下当前最后一次拿取的记录，就是6，</li><li>假设在t2时刻发布了新的记录，此时这个11放到最顶上，但是不会影响我们之前记录的6，此时t3时刻来拿第二页，这个时候拿数据就从6后一点的5去拿，就拿到了5-1的记录。</li><li>采用sortedSet来做，可以进行范围查询，并且还可以记录当前获取数据时间戳最小值，就可以实现滚动分页了</li></ul><p><img src="https://img.jwt1399.top/img/202212021653037.png"></p><p><strong>Redis情况模拟：</strong></p><pre class="line-numbers language-sql"><code class="language-sql"><span class="token comment" spellcheck="true"># 通过分数倒序查询</span>ZREVRANGEBYSCORE <span class="token keyword">key</span> max min <span class="token punctuation">[</span>WITHSCORES<span class="token punctuation">]</span> <span class="token punctuation">[</span><span class="token keyword">LIMIT</span> <span class="token keyword">offset</span> count<span class="token punctuation">]</span>max：最大分数值min：最小分数值<span class="token keyword">offset</span>：偏移量，返回结果的起始位置count：查询个数，返回结果数量<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-sql"><code class="language-sql"><span class="token number">127.0</span><span class="token punctuation">.</span><span class="token number">0.1</span>:<span class="token number">6379</span><span class="token operator">></span> ZREVRANGEBYSCORE zkey <span class="token number">1000</span> <span class="token number">0</span> WITHSCORES <span class="token keyword">LIMIT</span> <span class="token number">0</span> <span class="token number">3</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"m7"</span><span class="token number">2</span><span class="token punctuation">)</span> <span class="token string">"7"</span><span class="token number">3</span><span class="token punctuation">)</span> <span class="token string">"m6"</span><span class="token number">4</span><span class="token punctuation">)</span> <span class="token string">"6"</span><span class="token number">5</span><span class="token punctuation">)</span> <span class="token string">"m5"</span><span class="token number">6</span><span class="token punctuation">)</span> <span class="token string">"5"</span><span class="token number">127.0</span><span class="token punctuation">.</span><span class="token number">0.1</span>:<span class="token number">6379</span><span class="token operator">></span> ZADD zkey <span class="token number">8</span> m8<span class="token punctuation">(</span><span class="token keyword">integer</span><span class="token punctuation">)</span> <span class="token number">1</span><span class="token comment" spellcheck="true"># 没有查到重复数据</span><span class="token number">127.0</span><span class="token punctuation">.</span><span class="token number">0.1</span>:<span class="token number">6379</span><span class="token operator">></span> ZREVRANGEBYSCORE zkey <span class="token number">5</span> <span class="token number">0</span> WITHSCORES <span class="token keyword">LIMIT</span> <span class="token number">1</span> <span class="token number">3</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"m4"</span><span class="token number">2</span><span class="token punctuation">)</span> <span class="token string">"4"</span><span class="token number">3</span><span class="token punctuation">)</span> <span class="token string">"m3"</span><span class="token number">4</span><span class="token punctuation">)</span> <span class="token string">"3"</span><span class="token number">5</span><span class="token punctuation">)</span> <span class="token string">"m2"</span><span class="token number">6</span><span class="token punctuation">)</span> <span class="token string">"2"</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>具体操作如下：</p><ul><li><p>1、每次查询完成后，我们要得到查询出数据的最小时间戳，这个值会作为下一次查询的条件</p></li><li><p>2、我们需要找到与上一次查询相同的查询个数作为偏移量，下次查询时，跳过这些查询过的数据，拿到我们需要的数据</p></li></ul><p>综上：我们的请求参数中就需要携带两个参数</p><ul><li>lastId：上一次查询的最小时间戳</li><li>offset：偏移量这两个参数。</li></ul><p>这两个参数第一次会由前端来指定，以后的查询就根据后台结果作为条件，再次传递到后台。</p><p><img src="https://img.jwt1399.top/img/202212021656095.png"></p><ul><li>返回值实体类</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Data</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ScrollResult</span> <span class="token punctuation">{</span>    <span class="token keyword">private</span> List<span class="token operator">&lt;</span><span class="token operator">?</span><span class="token operator">></span> list<span class="token punctuation">;</span>    <span class="token keyword">private</span> Long minTime<span class="token punctuation">;</span>    <span class="token keyword">private</span> Integer offset<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>BlogController</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@GetMapping</span><span class="token punctuation">(</span><span class="token string">"/of/follow"</span><span class="token punctuation">)</span><span class="token keyword">public</span> Result <span class="token function">queryBlogOfFollow</span><span class="token punctuation">(</span>    <span class="token annotation punctuation">@RequestParam</span><span class="token punctuation">(</span><span class="token string">"lastId"</span><span class="token punctuation">)</span> Long max<span class="token punctuation">,</span> <span class="token annotation punctuation">@RequestParam</span><span class="token punctuation">(</span>value <span class="token operator">=</span> <span class="token string">"offset"</span><span class="token punctuation">,</span> defaultValue <span class="token operator">=</span> <span class="token string">"0"</span><span class="token punctuation">)</span> Integer offset<span class="token punctuation">)</span><span class="token punctuation">{</span>    <span class="token keyword">return</span> blogService<span class="token punctuation">.</span><span class="token function">queryBlogOfFollow</span><span class="token punctuation">(</span>max<span class="token punctuation">,</span> offset<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>BlogServiceImpl</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Override</span><span class="token keyword">public</span> Result <span class="token function">queryBlogOfFollow</span><span class="token punctuation">(</span>Long max<span class="token punctuation">,</span> Integer offset<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 1.获取当前用户</span>    Long userId <span class="token operator">=</span> UserHolder<span class="token punctuation">.</span><span class="token function">getUser</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 2.查询收件箱 ZREVRANGEBYSCORE key Max Min LIMIT offset count</span>    String key <span class="token operator">=</span> FEED_KEY <span class="token operator">+</span> userId<span class="token punctuation">;</span>    Set<span class="token operator">&lt;</span>ZSetOperations<span class="token punctuation">.</span>TypedTuple<span class="token operator">&lt;</span>String<span class="token operator">>></span> typedTuples <span class="token operator">=</span> stringRedisTemplate<span class="token punctuation">.</span><span class="token function">opsForZSet</span><span class="token punctuation">(</span><span class="token punctuation">)</span>        <span class="token punctuation">.</span><span class="token function">reverseRangeByScoreWithScores</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> max<span class="token punctuation">,</span> offset<span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 3.非空判断</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>typedTuples <span class="token operator">==</span> null <span class="token operator">||</span> typedTuples<span class="token punctuation">.</span><span class="token function">isEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">ok</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">// 4.解析数据：blogId、minTime（时间戳）、offset</span>    List<span class="token operator">&lt;</span>Long<span class="token operator">></span> ids <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ArrayList</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span>typedTuples<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">long</span> minTime <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 2</span>    <span class="token keyword">int</span> os <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 2</span>    <span class="token keyword">for</span> <span class="token punctuation">(</span>ZSetOperations<span class="token punctuation">.</span>TypedTuple<span class="token operator">&lt;</span>String<span class="token operator">></span> tuple <span class="token operator">:</span> typedTuples<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// 5 4 4 2 2</span>        <span class="token comment" spellcheck="true">// 4.1.获取id</span>        ids<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>Long<span class="token punctuation">.</span><span class="token function">valueOf</span><span class="token punctuation">(</span>tuple<span class="token punctuation">.</span><span class="token function">getValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 4.2.获取分数(时间戳）</span>        <span class="token keyword">long</span> time <span class="token operator">=</span> tuple<span class="token punctuation">.</span><span class="token function">getScore</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">longValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>time <span class="token operator">==</span> minTime<span class="token punctuation">)</span><span class="token punctuation">{</span>            os<span class="token operator">++</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span><span class="token keyword">else</span><span class="token punctuation">{</span>            minTime <span class="token operator">=</span> time<span class="token punctuation">;</span>            os <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span>    os <span class="token operator">=</span> minTime <span class="token operator">==</span> max <span class="token operator">?</span> os <span class="token operator">:</span> os <span class="token operator">+</span> offset<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 5.根据id查询blog</span>    String idStr <span class="token operator">=</span> StrUtil<span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span><span class="token string">","</span><span class="token punctuation">,</span> ids<span class="token punctuation">)</span><span class="token punctuation">;</span>    List<span class="token operator">&lt;</span>Blog<span class="token operator">></span> blogs <span class="token operator">=</span> <span class="token function">query</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">in</span><span class="token punctuation">(</span><span class="token string">"id"</span><span class="token punctuation">,</span> ids<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">last</span><span class="token punctuation">(</span><span class="token string">"ORDER BY FIELD(id,"</span> <span class="token operator">+</span> idStr <span class="token operator">+</span> <span class="token string">")"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">list</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">for</span> <span class="token punctuation">(</span>Blog blog <span class="token operator">:</span> blogs<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 5.1.查询blog有关的用户</span>        <span class="token function">queryBlogUser</span><span class="token punctuation">(</span>blog<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 5.2.查询blog是否被点赞</span>        <span class="token function">isBlogLiked</span><span class="token punctuation">(</span>blog<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">// 6.封装并返回</span>    ScrollResult r <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ScrollResult</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    r<span class="token punctuation">.</span><span class="token function">setList</span><span class="token punctuation">(</span>blogs<span class="token punctuation">)</span><span class="token punctuation">;</span>    r<span class="token punctuation">.</span><span class="token function">setOffset</span><span class="token punctuation">(</span>os<span class="token punctuation">)</span><span class="token punctuation">;</span>    r<span class="token punctuation">.</span><span class="token function">setMinTime</span><span class="token punctuation">(</span>minTime<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">ok</span><span class="token punctuation">(</span>r<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h1 id="⑩附近的商户-GEO"><a href="#⑩附近的商户-GEO" class="headerlink" title="⑩附近的商户-GEO"></a>⑩附近的商户-GEO</h1><h2 id="❶GEO数据结构的基本用法"><a href="#❶GEO数据结构的基本用法" class="headerlink" title="❶GEO数据结构的基本用法"></a>❶GEO数据结构的基本用法</h2><p>GEO就是Geolocation的简写形式，代表地理坐标。Redis在3.2版本中加入了对GEO的支持，允许存储地理坐标信息，帮助我们根据经纬度来检索数据。常见的命令有：</p><ul><li>GEOADD：添加一个地理空间信息，包含：经度（longitude）、纬度（latitude）、值（member）</li><li>GEODIST：计算指定的两个点之间的距离并返回</li><li>GEOHASH：将指定member的坐标转为hash字符串形式并返回</li><li>GEOPOS：返回指定member的坐标</li><li><del>GEORADIUS：指定圆心、半径，找到该圆内包含的所有member，并按照与圆心之间的距离排序后返回。6.以后已废弃</del></li><li>GEOSEARCH：在指定范围内搜索member，并按照与指定点之间的距离排序后返回。范围可以是圆形或矩形。6.2.新功能</li><li>GEOSEARCHSTORE：与GEOSEARCH功能一致，不过可以把结果存储到一个指定的key。 6.2.新功能</li></ul><blockquote><p>练习Redis的GEO功能</p><ul><li><p>1.添加下面几条数据：</p><ul><li><p>北京南站（ 116.378248 39.865275 ）</p></li><li><p>北京站（ 116.42803 39.903738 ）</p></li><li><p>北京西站（ 116.322287 39.893729 ）</p></li></ul></li></ul><pre class="line-numbers language-sql"><code class="language-sql"><span class="token number">127.0</span><span class="token punctuation">.</span><span class="token number">0.1</span>:<span class="token number">6379</span><span class="token operator">></span> GEOADd gkey <span class="token number">116.378248</span> <span class="token number">39.865275</span> bjn <span class="token number">116.42803</span> <span class="token number">39.903738</span> bj <span class="token number">116.322287</span> <span class="token number">39.893729</span> bjx<span class="token punctuation">(</span><span class="token keyword">integer</span><span class="token punctuation">)</span> <span class="token number">3</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><ul><li>2.计算北京西站到北京站的距离</li></ul><pre class="line-numbers language-sql"><code class="language-sql"><span class="token number">127.0</span><span class="token punctuation">.</span><span class="token number">0.1</span>:<span class="token number">6379</span><span class="token operator">></span> GEODIST gkey bjx bj km<span class="token string">"9.0916"</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><ul><li>3.搜索天安门（ 116.397904 39.909005 ）附近10km内的所有火车站，并按照距离升序排序</li></ul><pre class="line-numbers language-sql"><code class="language-sql"><span class="token number">127.0</span><span class="token punctuation">.</span><span class="token number">0.1</span>:<span class="token number">6379</span><span class="token operator">></span> GEOSEARCH gkey FROMLONLAT <span class="token number">116.397904</span> <span class="token number">39.909005</span> BYRADIUS <span class="token number">10</span> km<span class="token number">1</span><span class="token punctuation">)</span> <span class="token string">"bj"</span><span class="token number">2</span><span class="token punctuation">)</span> <span class="token string">"bjn"</span><span class="token number">3</span><span class="token punctuation">)</span> <span class="token string">"bjx"</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre></blockquote><h2 id="❷导入店铺数据到GEO"><a href="#❷导入店铺数据到GEO" class="headerlink" title="❷导入店铺数据到GEO"></a>❷导入店铺数据到GEO</h2><p>当我们点击美食之后，会出现一系列的商家，商家中可以按照多种排序方式，我们此时关注的是距离，这个地方就需要使用到我们的GEO，向后台传入当前app收集的地址(我们此处是写死的) ，以当前坐标作为圆心，同时绑定相同的店家类型type，以及分页信息，把这几个条件传入后台，后台查询出对应的数据再返回。</p><p><img src="https://img.jwt1399.top/img/202212031520795.png"></p><p>我们要做的事情是：将数据库表中的数据导入到redis中去，redis中的GEO，GEO在redis中是一个menber和一个经纬度，我们把x和y传入到经纬度位置去，但我们不能把所有的数据都放入到menber中去，毕竟作为redis是一个内存级数据库，如果存海量数据，redis还是力不从心，所以我们在这个地方存储他的id即可。</p><p>但是这个时候还有一个问题，就是在redis中并没有存储type，所以我们无法根据type来对数据进行筛选，所以我们可以按照商户类型做分组，类型相同的商户作为同一组，以typeId为key存入同一个GEO集合中即可</p><p><img src="/../images/Redis-%E5%AE%9E%E6%88%98%E7%AF%87/image-20221203152526081.png"></p><p>HmDianPingApplicationTests</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Test</span><span class="token keyword">void</span> <span class="token function">loadShopData</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 1.查询店铺信息</span>    List<span class="token operator">&lt;</span>Shop<span class="token operator">></span> list <span class="token operator">=</span> shopService<span class="token punctuation">.</span><span class="token function">list</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 2.把店铺分组，按照typeId分组，typeId一致的放到一个集合</span>    Map<span class="token operator">&lt;</span>Long<span class="token punctuation">,</span> List<span class="token operator">&lt;</span>Shop<span class="token operator">>></span> map <span class="token operator">=</span> list<span class="token punctuation">.</span><span class="token function">stream</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">collect</span><span class="token punctuation">(</span>Collectors<span class="token punctuation">.</span><span class="token function">groupingBy</span><span class="token punctuation">(</span>Shop<span class="token operator">:</span><span class="token operator">:</span>getTypeId<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 3.分批完成写入Redis</span>    <span class="token keyword">for</span> <span class="token punctuation">(</span>Map<span class="token punctuation">.</span>Entry<span class="token operator">&lt;</span>Long<span class="token punctuation">,</span> List<span class="token operator">&lt;</span>Shop<span class="token operator">>></span> entry <span class="token operator">:</span> map<span class="token punctuation">.</span><span class="token function">entrySet</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 3.1.获取类型id</span>        Long typeId <span class="token operator">=</span> entry<span class="token punctuation">.</span><span class="token function">getKey</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        String key <span class="token operator">=</span> SHOP_GEO_KEY <span class="token operator">+</span> typeId<span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 3.2.获取同类型的店铺的集合</span>        List<span class="token operator">&lt;</span>Shop<span class="token operator">></span> value <span class="token operator">=</span> entry<span class="token punctuation">.</span><span class="token function">getValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        List<span class="token operator">&lt;</span>RedisGeoCommands<span class="token punctuation">.</span>GeoLocation<span class="token operator">&lt;</span>String<span class="token operator">>></span> locations <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ArrayList</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span>value<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 3.3.写入redis GEOADD key 经度 纬度 member</span>        <span class="token keyword">for</span> <span class="token punctuation">(</span>Shop shop <span class="token operator">:</span> value<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// stringRedisTemplate.opsForGeo().add(key, new Point(shop.getX(), shop.getY()), shop.getId().toString());</span>            locations<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">RedisGeoCommands<span class="token punctuation">.</span>GeoLocation</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span>                    shop<span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>                    <span class="token keyword">new</span> <span class="token class-name">Point</span><span class="token punctuation">(</span>shop<span class="token punctuation">.</span><span class="token function">getX</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> shop<span class="token punctuation">.</span><span class="token function">getY</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>            <span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        stringRedisTemplate<span class="token punctuation">.</span><span class="token function">opsForGeo</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> locations<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="❸实现附近商户功能"><a href="#❸实现附近商户功能" class="headerlink" title="❸实现附近商户功能"></a>❸实现附近商户功能</h2><p>SpringDataRedis的2.3.9版本并不支持Redis 6.2提供的GEOSEARCH命令，因此我们需要提升其版本，修改自己的POM</p><p>第一步：导入pom</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token comment" spellcheck="true">&lt;!--排除默认的--></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework.boot<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-boot-starter-data-redis<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>exclusions</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>exclusion</span><span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-data-redis<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework.data<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>exclusion</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>exclusion</span><span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>lettuce-core<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>            <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>io.lettuce<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>        <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>exclusion</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>exclusions</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span class="token comment" spellcheck="true">&lt;!--添加自己的--></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework.data<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-data-redis<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>2.6.2<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>io.lettuce<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>lettuce-core<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>6.1.6.RELEASE<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>第二步：</p><p>ShopController</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@GetMapping</span><span class="token punctuation">(</span><span class="token string">"/of/type"</span><span class="token punctuation">)</span><span class="token keyword">public</span> Result <span class="token function">queryShopByType</span><span class="token punctuation">(</span>        <span class="token annotation punctuation">@RequestParam</span><span class="token punctuation">(</span><span class="token string">"typeId"</span><span class="token punctuation">)</span> Integer typeId<span class="token punctuation">,</span>        <span class="token annotation punctuation">@RequestParam</span><span class="token punctuation">(</span>value <span class="token operator">=</span> <span class="token string">"current"</span><span class="token punctuation">,</span> defaultValue <span class="token operator">=</span> <span class="token string">"1"</span><span class="token punctuation">)</span> Integer current<span class="token punctuation">,</span>        <span class="token annotation punctuation">@RequestParam</span><span class="token punctuation">(</span>value <span class="token operator">=</span> <span class="token string">"x"</span><span class="token punctuation">,</span> required <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">)</span> Double x<span class="token punctuation">,</span>        <span class="token annotation punctuation">@RequestParam</span><span class="token punctuation">(</span>value <span class="token operator">=</span> <span class="token string">"y"</span><span class="token punctuation">,</span> required <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">)</span> Double y<span class="token punctuation">)</span> <span class="token punctuation">{</span>   <span class="token keyword">return</span> shopService<span class="token punctuation">.</span><span class="token function">queryShopByType</span><span class="token punctuation">(</span>typeId<span class="token punctuation">,</span> current<span class="token punctuation">,</span> x<span class="token punctuation">,</span> y<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>ShopServiceImpl</p><pre class="line-numbers language-java"><code class="language-java">        <span class="token annotation punctuation">@Override</span>    <span class="token keyword">public</span> Result <span class="token function">queryShopByType</span><span class="token punctuation">(</span>Integer typeId<span class="token punctuation">,</span> Integer current<span class="token punctuation">,</span> Double x<span class="token punctuation">,</span> Double y<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 1.判断是否需要根据坐标查询</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>x <span class="token operator">==</span> null <span class="token operator">||</span> y <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 不需要坐标查询，按数据库查询</span>            Page<span class="token operator">&lt;</span>Shop<span class="token operator">></span> page <span class="token operator">=</span> <span class="token function">query</span><span class="token punctuation">(</span><span class="token punctuation">)</span>                    <span class="token punctuation">.</span><span class="token function">eq</span><span class="token punctuation">(</span><span class="token string">"type_id"</span><span class="token punctuation">,</span> typeId<span class="token punctuation">)</span>                    <span class="token punctuation">.</span><span class="token function">page</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Page</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span>current<span class="token punctuation">,</span> SystemConstants<span class="token punctuation">.</span>DEFAULT_PAGE_SIZE<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">// 返回数据</span>            <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">ok</span><span class="token punctuation">(</span>page<span class="token punctuation">.</span><span class="token function">getRecords</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 2.计算分页参数</span>        <span class="token keyword">int</span> from <span class="token operator">=</span> <span class="token punctuation">(</span>current <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">*</span> SystemConstants<span class="token punctuation">.</span>DEFAULT_PAGE_SIZE<span class="token punctuation">;</span>        <span class="token keyword">int</span> end <span class="token operator">=</span> current <span class="token operator">*</span> SystemConstants<span class="token punctuation">.</span>DEFAULT_PAGE_SIZE<span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 3.查询redis、按照距离排序、分页。结果：shopId、distance</span>        String key <span class="token operator">=</span> SHOP_GEO_KEY <span class="token operator">+</span> typeId<span class="token punctuation">;</span>        GeoResults<span class="token operator">&lt;</span>RedisGeoCommands<span class="token punctuation">.</span>GeoLocation<span class="token operator">&lt;</span>String<span class="token operator">>></span> results <span class="token operator">=</span> stringRedisTemplate<span class="token punctuation">.</span><span class="token function">opsForGeo</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// GEOSEARCH key BYLONLAT x y BYRADIUS 10 WITHDISTANCE</span>                <span class="token punctuation">.</span><span class="token function">search</span><span class="token punctuation">(</span>                        key<span class="token punctuation">,</span>                        GeoReference<span class="token punctuation">.</span><span class="token function">fromCoordinate</span><span class="token punctuation">(</span>x<span class="token punctuation">,</span> y<span class="token punctuation">)</span><span class="token punctuation">,</span>                        <span class="token keyword">new</span> <span class="token class-name">Distance</span><span class="token punctuation">(</span><span class="token number">5000</span><span class="token punctuation">)</span><span class="token punctuation">,</span>                        RedisGeoCommands<span class="token punctuation">.</span>GeoSearchCommandArgs<span class="token punctuation">.</span><span class="token function">newGeoSearchArgs</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">includeDistance</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">limit</span><span class="token punctuation">(</span>end<span class="token punctuation">)</span>                <span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 4.解析出id</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>results <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">ok</span><span class="token punctuation">(</span>Collections<span class="token punctuation">.</span><span class="token function">emptyList</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        List<span class="token operator">&lt;</span>GeoResult<span class="token operator">&lt;</span>RedisGeoCommands<span class="token punctuation">.</span>GeoLocation<span class="token operator">&lt;</span>String<span class="token operator">>>></span> list <span class="token operator">=</span> results<span class="token punctuation">.</span><span class="token function">getContent</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>list<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">&lt;=</span> from<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 没有下一页了，结束</span>            <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">ok</span><span class="token punctuation">(</span>Collections<span class="token punctuation">.</span><span class="token function">emptyList</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 4.1.截取 from ~ end的部分</span>        List<span class="token operator">&lt;</span>Long<span class="token operator">></span> ids <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ArrayList</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span>list<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        Map<span class="token operator">&lt;</span>String<span class="token punctuation">,</span> Distance<span class="token operator">></span> distanceMap <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">HashMap</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span>list<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        list<span class="token punctuation">.</span><span class="token function">stream</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">skip</span><span class="token punctuation">(</span>from<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span>result <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 4.2.获取店铺id</span>            String shopIdStr <span class="token operator">=</span> result<span class="token punctuation">.</span><span class="token function">getContent</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            ids<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>Long<span class="token punctuation">.</span><span class="token function">valueOf</span><span class="token punctuation">(</span>shopIdStr<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">// 4.3.获取距离</span>            Distance distance <span class="token operator">=</span> result<span class="token punctuation">.</span><span class="token function">getDistance</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            distanceMap<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span>shopIdStr<span class="token punctuation">,</span> distance<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 5.根据id查询Shop</span>        String idStr <span class="token operator">=</span> StrUtil<span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span><span class="token string">","</span><span class="token punctuation">,</span> ids<span class="token punctuation">)</span><span class="token punctuation">;</span>        List<span class="token operator">&lt;</span>Shop<span class="token operator">></span> shops <span class="token operator">=</span> <span class="token function">query</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">in</span><span class="token punctuation">(</span><span class="token string">"id"</span><span class="token punctuation">,</span> ids<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">last</span><span class="token punctuation">(</span><span class="token string">"ORDER BY FIELD(id,"</span> <span class="token operator">+</span> idStr <span class="token operator">+</span> <span class="token string">")"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">list</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">for</span> <span class="token punctuation">(</span>Shop shop <span class="token operator">:</span> shops<span class="token punctuation">)</span> <span class="token punctuation">{</span>            shop<span class="token punctuation">.</span><span class="token function">setDistance</span><span class="token punctuation">(</span>distanceMap<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>shop<span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 6.返回</span>        <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">ok</span><span class="token punctuation">(</span>shops<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h1 id="⑪用户签到-BitMap"><a href="#⑪用户签到-BitMap" class="headerlink" title="⑪用户签到-BitMap"></a>⑪用户签到-BitMap</h1><h2 id="❶思路"><a href="#❶思路" class="headerlink" title="❶思路"></a>❶思路</h2><p>针对签到功能完全可以通过MySQL来完成，用户一次签到，就是一条记录，假如有1000万用户，平均每人每年签到次数为10次，则这张表一年的数据量为1亿条，假设每次签到需要使用20字节的内存，一个月则需要600多字节。</p><p>我们如何能够简化一点呢？采用Redis的BitMap</p><p>我们按月来统计用户签到信息，把每一个bit位对应当月的每一天，形成了映射关系。用0和1标示业务状态，签到记录为1，未签到则记录为0。这样我们就用极小的空间，来实现了大量数据的表示。</p><p>Redis中是利用string类型数据结构实现BitMap，因此最大上限是512M，转换为bit则是 2^32个bit位。</p><p><img src="https://img.jwt1399.top/img/202212041337440.png"></p><p>BitMap的操作命令有：</p><ul><li>SETBIT：向指定位置（offset）存入一个0或1</li><li>GETBIT ：获取指定位置（offset）的bit值</li><li>BITCOUNT ：统计BitMap中值为1的bit位的数量</li><li>BITFIELD ：操作（查询、修改、自增）BitMap中bit数组中的指定位置（offset）的值<ul><li>u 代表无符号位，i 代表有符号位</li></ul></li><li>BITFIELD_RO ：获取BitMap中bit数组，并以十进制形式返回</li><li>BITOP ：将多个BitMap的结果做位运算（与 、或、异或）</li><li>BITPOS ：查找bit数组中指定范围内第一个0或1出现的位置</li></ul><h2 id="❷实现签到功能"><a href="#❷实现签到功能" class="headerlink" title="❷实现签到功能"></a>❷实现签到功能</h2><blockquote><p>需求：实现签到接口，将当前用户当天签到信息保存到Redis中</p></blockquote><ul><li>UserController</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@PostMapping</span><span class="token punctuation">(</span><span class="token string">"/sign"</span><span class="token punctuation">)</span><span class="token keyword">public</span> Result <span class="token function">sign</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>  <span class="token keyword">return</span> userService<span class="token punctuation">.</span><span class="token function">sign</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><ul><li>UserServiceImpl</li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Override</span><span class="token keyword">public</span> Result <span class="token function">sign</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 1.获取当前登录用户</span>    Long userId <span class="token operator">=</span> UserHolder<span class="token punctuation">.</span><span class="token function">getUser</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 2.获取日期</span>    LocalDateTime now <span class="token operator">=</span> LocalDateTime<span class="token punctuation">.</span><span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 3.拼接key</span>    String keySuffix <span class="token operator">=</span> now<span class="token punctuation">.</span><span class="token function">format</span><span class="token punctuation">(</span>DateTimeFormatter<span class="token punctuation">.</span><span class="token function">ofPattern</span><span class="token punctuation">(</span><span class="token string">":yyyyMM"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    String key <span class="token operator">=</span> USER_SIGN_KEY <span class="token operator">+</span> userId <span class="token operator">+</span> keySuffix<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 4.获取今天是本月的第几天</span>    <span class="token keyword">int</span> dayOfMonth <span class="token operator">=</span> now<span class="token punctuation">.</span><span class="token function">getDayOfMonth</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 5.写入Redis SETBIT key offset 1</span>    stringRedisTemplate<span class="token punctuation">.</span><span class="token function">opsForValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">setBit</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> dayOfMonth <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">ok</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="❸签到统计"><a href="#❸签到统计" class="headerlink" title="❸签到统计"></a>❸签到统计</h2><p><strong>问题1：</strong>什么叫做连续签到天数？</p><p>从最后一次签到开始向前统计，直到遇到第一次未签到为止，计算总的签到次数，就是连续签到天数。</p><p>Java逻辑代码：获得当前这个月的最后一次签到数据，定义一个计数器，然后不停的向前统计，直到获得第一个非1的数字即可，每得到一个非0的数字计数器+1，直到遍历完所有的数据，就可以获得连续签到天数了</p><p><strong>问题2：</strong>如何得到本月到今天为止的所有签到数据？</p><pre class="line-numbers language-sql"><code class="language-sql">BITFIELD <span class="token keyword">key</span> GET u<span class="token punctuation">[</span>dayOfMonth<span class="token punctuation">]</span> <span class="token number">0</span><span class="token comment" spellcheck="true"># u[dayOfMonth] 获取dayOfMonth个bit位</span><span class="token comment" spellcheck="true"># 返回结果为10进制</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>假设今天是10号，那么我们就可以从当前月的第一天开始，获得到当前这一天的位数，是10号，那么就是10位，去拿这段时间的数据，就能拿到所有的数据了，那么这10天里边签到了多少次呢？统计有多少个1即可。</p><p><strong>问题3：如何从后向前遍历每个bit位？</strong></p><p>注意：BITFIELD返回的数据是10进制，假如说返回一个数字8，那么我哪儿知道到底哪些是0，哪些是1呢？我们只需要让得到的10进制数字和1做与运算就可以了，因为1只有遇见1 才是1，其他数字都是0 ，我们把签到结果和1进行与操作，每与一次，就把签到结果向右移动一位，依次内推，我们就能完成逐个遍历的效果了。</p><blockquote><p>需求：实现接口，统计当前用户截止当前时间在本月的连续签到天数</p></blockquote><ul><li><strong>UserController</strong></li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@GetMapping</span><span class="token punctuation">(</span><span class="token string">"/sign/count"</span><span class="token punctuation">)</span><span class="token keyword">public</span> Result <span class="token function">signCount</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>    <span class="token keyword">return</span> userService<span class="token punctuation">.</span><span class="token function">signCount</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><ul><li><strong>UserServiceImpl</strong></li></ul><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Override</span><span class="token keyword">public</span> Result <span class="token function">signCount</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 1.获取当前登录用户</span>    Long userId <span class="token operator">=</span> UserHolder<span class="token punctuation">.</span><span class="token function">getUser</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 2.获取日期</span>    LocalDateTime now <span class="token operator">=</span> LocalDateTime<span class="token punctuation">.</span><span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 3.拼接key</span>    String keySuffix <span class="token operator">=</span> now<span class="token punctuation">.</span><span class="token function">format</span><span class="token punctuation">(</span>DateTimeFormatter<span class="token punctuation">.</span><span class="token function">ofPattern</span><span class="token punctuation">(</span><span class="token string">":yyyyMM"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    String key <span class="token operator">=</span> USER_SIGN_KEY <span class="token operator">+</span> userId <span class="token operator">+</span> keySuffix<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 4.获取今天是本月的第几天</span>    <span class="token keyword">int</span> dayOfMonth <span class="token operator">=</span> now<span class="token punctuation">.</span><span class="token function">getDayOfMonth</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>   <span class="token comment" spellcheck="true">// 5.获取本月截止今天为止的所有的签到记录，返回的是一个十进制的数字 BITFIELD sign:5:202203 GET u14 0</span>    List<span class="token operator">&lt;</span>Long<span class="token operator">></span> result <span class="token operator">=</span> stringRedisTemplate<span class="token punctuation">.</span><span class="token function">opsForValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">bitField</span><span class="token punctuation">(</span>            key<span class="token punctuation">,</span>            BitFieldSubCommands<span class="token punctuation">.</span><span class="token function">create</span><span class="token punctuation">(</span><span class="token punctuation">)</span>                    <span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>BitFieldSubCommands<span class="token punctuation">.</span>BitFieldType<span class="token punctuation">.</span><span class="token function">unsigned</span><span class="token punctuation">(</span>dayOfMonth<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">valueAt</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span>    <span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>result <span class="token operator">==</span> null <span class="token operator">||</span> result<span class="token punctuation">.</span><span class="token function">isEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 没有任何签到结果</span>        <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">ok</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    Long num <span class="token operator">=</span> result<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>num <span class="token operator">==</span> null <span class="token operator">||</span> num <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">ok</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">// 6.循环遍历</span>    <span class="token keyword">int</span> count <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>    <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 6.1.让这个数字与1做与运算，得到数字的最后一个bit位  // 判断这个bit位是否为0</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>num <span class="token operator">&amp;</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 如果为0，说明未签到，结束</span>            <span class="token keyword">break</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span><span class="token keyword">else</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 如果不为0，说明已签到，计数器+1</span>            count<span class="token operator">++</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 把数字右移一位，抛弃最后一个bit位，继续下一个bit位</span>        num <span class="token operator">>>>=</span> <span class="token number">1</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">return</span> Result<span class="token punctuation">.</span><span class="token function">ok</span><span class="token punctuation">(</span>count<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h1 id="⑫UV统计-HyperLogLog"><a href="#⑫UV统计-HyperLogLog" class="headerlink" title="⑫UV统计-HyperLogLog"></a>⑫UV统计-HyperLogLog</h1><p>首先我们搞懂两个概念：</p><ul><li>UV：全称Unique Visitor，也叫独立访客量，是指通过互联网访问、浏览这个网页的自然人。1天内同一个用户多次访问该网站，只记录1次。</li><li>PV：全称Page View，也叫页面访问量或点击量，用户每访问网站的一个页面，记录1次PV，用户多次打开页面，则记录多次PV。往往用来衡量网站的流量。</li></ul><p>通常来说UV会比PV大很多，所以衡量同一个网站的访问量，我们需要综合考虑很多因素，所以我们只是单纯的把这两个值作为一个参考值</p><p>UV统计在服务端做会比较麻烦，因为要判断该用户是否已经统计过了，需要将统计过的用户信息保存。但是如果每个访问的用户都保存到Redis中，数据量会非常恐怖，那怎么处理呢？</p><p>Hyperloglog(HLL) 是从LogLog算法派生的概率算法，用于确定非常大的集合的基数，而不需要存储其所有值。</p><p>相关算法原理大家可以参考：<a href="https://juejin.cn/post/6844903785744056333#heading-0">https://juejin.cn/post/6844903785744056333#heading-0</a></p><p>Redis中的HLL是基于string结构实现的，单个HLL的内存<strong>永远小于16kb</strong>，<strong>内存占用低</strong>的令人发指！作为代价，其测</p><p>量结果是概率性的，<strong>有小于0.81％的误差</strong>。不过对于UV统计来说，这完全可以忽略。</p><p>测试思路：我们直接利用单元测试，向HyperLogLog中添加100万条数据，看看内存占用和统计效果如何</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Test</span><span class="token keyword">void</span> <span class="token function">testHyperLogLog</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>  <span class="token comment" spellcheck="true">//插入一百万条数据，1000条插入一次</span>  <span class="token comment" spellcheck="true">// 准备数组，装用户数据</span>  String<span class="token punctuation">[</span><span class="token punctuation">]</span> users <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">String</span><span class="token punctuation">[</span><span class="token number">1000</span><span class="token punctuation">]</span><span class="token punctuation">;</span>  <span class="token comment" spellcheck="true">// 数组角标</span>  <span class="token keyword">int</span> index <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>  <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> i <span class="token operator">&lt;=</span> <span class="token number">1000000</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">// 赋值</span>    users<span class="token punctuation">[</span>index<span class="token operator">++</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token string">"user_"</span> <span class="token operator">+</span> i<span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 每1000条发送一次</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>i <span class="token operator">%</span> <span class="token number">1000</span> <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>      index <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>      <span class="token comment" spellcheck="true">//PFADD</span>      stringRedisTemplate<span class="token punctuation">.</span><span class="token function">opsForHyperLogLog</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token string">"hll1"</span><span class="token punctuation">,</span> users<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>  <span class="token punctuation">}</span>  <span class="token comment" spellcheck="true">// 统计数量 PFCOUNT</span>  Long size <span class="token operator">=</span> stringRedisTemplate<span class="token punctuation">.</span><span class="token function">opsForHyperLogLog</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token string">"hll1"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"size = "</span> <span class="token operator">+</span> size<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>经过测试：我们会发生他的误差是在允许范围内，并且内存占用极小</p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>基于 Spring Boot + Redis 的店铺点评 APP，实现了找店铺 &#x3D;&gt; 写点评 &#x3D;&gt; 看热评 &#x3D;&gt; 点赞关注 &#x3D;&gt; 关注 Feed 流的完整业务流程。</p><p>主要工作：</p><ol><li>短信登录：使用 Redis 实现分布式 Session，解决集群间登录态同步问题；使用 Hash 代替 String 来存储用户信息，节约了 xx% 的内存并便于单字段的修改。（需要自己实际测试对比数据，节省内存的原因是不用保存序列化对象信息或者 JSON 的一些额外字符串）</li><li>店铺查询：使用 Redis 对高频访问店铺进行缓存，降低 DB 压力同时提升 90% 的数据查询性能。</li><li>为方便其他业务后续使用缓存，使用泛型 + 函数式编程实现了通用缓存访问静态方法，并解决了缓存雪崩、缓存穿透等问题。</li><li>使用常量类全局管理 Redis Key 前缀、TTL 等，保证了键空间的业务隔离，减少冲突。</li><li>使用 Redis 的 Geo + Hash 数据结构分类存储附近商户，并使用 Geo Search 命令实现高性能商户查询及按距离排序。</li><li>使用 Redis List 数据结构存储用户点赞信息，并基于 ZSet 实现 TopN 点赞排行，实测相对于 DB 查询性能提升 xx%。（需要自己实际测试对比数据）</li><li>使用 Redis Set 数据结构实现用户关注、共同关注功能（交集），实测相对于 DB 查询性能提升 xx%。（需要自己实际测试对比数据）</li><li>使用 Redis BitMap 实现用户连续签到统计功能，相对于传统关系库存储，节约 xx% 的内存并提升 xx% 的查询性能。（需要自己实际测试对比数据）</li><li>在系统用户量不大的前提下，基于推模式实现关注 Feed 流，保证了新点评消息的及时可达，并减少用户访问的等待时间。</li><li>优惠券秒杀：使用 Redis + Lua 脚本实现库存预检，并通过 Stream 队列实现订单的异步创建，解决了超卖问题、实现一人一单。实现相比传统数据库，秒杀性能提高了 xx%。（需要自己实际测试对比数据）</li></ol><p>再列举一些该项目可以扩展的点，有能力的同学可以自己尝试实现：</p><ol><li>使用 Redis + Token 机制实现单点登录（补充到上述第 1 点中）</li><li>对 Redis 的所有 key 设置 N + n 的过期时间，从而合理使用内存并防止缓存雪崩；针对热点店铺缓存，使用逻辑过期（或自动续期）机制解决缓存击穿问题，防止数据库宕机。</li><li>使用 Redis 的 Geo + Hash 数据结构分类存储附近商户，并使用 Geo Search 命令实现高性能商户查询及按距离排序，实测相对于传统 DB 查询 + 业务层计算的方式，性能提升 xx%。</li><li>使用 Redis Set 数据结构实现用户关注、共同关注功能（交集），实测相对于 DB 查询性能提升 xx%，并使用 Redis AOF + 业务层日志防止关注数据丢失。（理解 AOF 和 RDB 持久化机制后再写这点）</li><li>基于 Spring Scheduler 实现对热点数据的定期检测和缓存预加载，提升用户的访问体验，并通过 Redisson 分布式锁保证集群中同一时刻的定时任务只执行一次。</li><li>关注 Feed 流可以改为推拉结合模式（活跃用户用推、普通用户用拉）</li><li>使用哨兵集群来提升 Redis 的读并发量、可用性和稳定性；或者使用 Redis 分片集群来提升 Redis 读写并发量、总存储容量，保障可用性和稳定性。</li><li>随着系统用户增多，使用 Redis HyperLogLog 代替 DB 来实现店铺和点评的 UV 统计，提高 xx% 的查询分析性能并解决 xx% 的内存空间。</li></ol><p><span style="color: gray"><font size=3>(Stars:400+)</font></span></p><p><span style="color: gray"><font size=3>(Pv:30w+)</font></span></p><h1 id="Sponsor❤️"><a href="#Sponsor❤️" class="headerlink" title="Sponsor❤️"></a>Sponsor❤️</h1><p>您的支持是我不断前进的动力，如果您感觉本文对您有所帮助的话，可以考虑打赏一下本文，用以维持本博客的运营费用，拒绝白嫖，从你我做起！🥰🥰🥰</p><table>  <tbody>     <tr>         <td style="text-align:center;">支付宝</td>         <td style="text-align:center;">微信</td>     </tr>   <tr>    <td style="text-align:center;" ><img width="200" src="https://jwt1399.top/medias/reward/alipay.png"></td>          <td style="text-align:center;"><img width="200" src="https://jwt1399.top/medias/reward/sponor_wechat.png"></td>       </tr></tbody></table>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;h1 id=&quot;⓪系统简介&quot;&gt;&lt;a href=&quot;#⓪系统简介&quot; class=&quot;headerlink&quot;</summary>
        
      
    
    
    
    <category term="SQL" scheme="https://jwt1399.top/categories/SQL/"/>
    
    
    <category term="NoSQL" scheme="https://jwt1399.top/tags/NoSQL/"/>
    
  </entry>
  
  <entry>
    <title>Redis-基础篇</title>
    <link href="https://jwt1399.top/posts/27273.html"/>
    <id>https://jwt1399.top/posts/27273.html</id>
    <published>2022-07-03T15:32:00.000Z</published>
    <updated>2022-12-04T06:44:36.665Z</updated>
    
    <content type="html"><![CDATA[<h1 id="一、Redis简介"><a href="#一、Redis简介" class="headerlink" title="一、Redis简介"></a>一、Redis简介</h1><h2 id="1-认识NoSQL"><a href="#1-认识NoSQL" class="headerlink" title="1.认识NoSQL"></a>1.认识NoSQL</h2><h3 id="1-1什么是NoSQL"><a href="#1-1什么是NoSQL" class="headerlink" title="1.1什么是NoSQL"></a>1.1什么是NoSQL</h3><hr><ul><li><p>NoSQL最常见的解释是”<code>non-relational</code>“，泛指<strong>非关系型的数据库</strong>，很多人也说它是”<em><strong>Not Only SQL</strong></em>“</p></li><li><p>NoSQL 不依赖业务逻辑方式存储，而以简单的key-value模式存储。因此大大的增加了数据库的扩展能力。</p></li><li><p>区别于关系数据库，它们不保证关系数据的ACID特性</p></li><li><p>常见的NoSQL数据库有：<code>Redis</code>、<code>MemCache</code>、<code>MongoDB</code>等</p></li></ul><h3 id="1-2-NoSQL适用场景"><a href="#1-2-NoSQL适用场景" class="headerlink" title="1.2 NoSQL适用场景"></a>1.2 NoSQL适用场景</h3><ul><li><p>对数据高并发的读写</p></li><li><p>海量数据的读写</p></li><li><p>对数据高可扩展性的</p></li></ul><h3 id="1-3-NoSQL不适用场景"><a href="#1-3-NoSQL不适用场景" class="headerlink" title="1.3  NoSQL不适用场景"></a>1.3  NoSQL不适用场景</h3><ul><li><p>需要事务支持</p></li><li><p>基于sql的结构化查询存储，处理复杂的关系，需要即席查询。</p></li></ul><h3 id="1-4NoSQL与SQL的差异"><a href="#1-4NoSQL与SQL的差异" class="headerlink" title="1.4NoSQL与SQL的差异"></a>1.4NoSQL与SQL的差异</h3><hr><table><thead><tr><th align="center"></th><th align="center">SQL</th><th align="center">NoSQL</th></tr></thead><tbody><tr><td align="center">数据结构</td><td align="center">结构化</td><td align="center">非结构化</td></tr><tr><td align="center">数据关联</td><td align="center">关联的</td><td align="center">无关联的</td></tr><tr><td align="center">查询方式</td><td align="center">SQL查询</td><td align="center">非SQL</td></tr><tr><td align="center">事务特性</td><td align="center">ACID</td><td align="center">BASE</td></tr><tr><td align="center">存储方式</td><td align="center">磁盘</td><td align="center">内存</td></tr><tr><td align="center">扩展性</td><td align="center">垂直</td><td align="center">水平</td></tr><tr><td align="center">使用场景</td><td align="center">1）数据结构固定<br>2）相关业务对数据安全性、一致性要求较高</td><td align="center">1）数据结构不固定<br>2）对一致性、安全性要求不高<br>3）对性能要求</td></tr></tbody></table><h2 id="2-认识Redis"><a href="#2-认识Redis" class="headerlink" title="2.认识Redis"></a>2.认识Redis</h2><blockquote><p>Redis诞生于2009年全称是Remote Dictionary Server，远程词典服务器，是一个基于内存的键值型NoSQL数据库。</p></blockquote><p><strong>Redis的特征：</strong></p><ul><li>键值（<code>key-value</code>）型，value支持多种不同数据结构，功能丰富</li><li>单线程，每个命令具备原子性</li><li>低延迟，速度快（基于内存、IO多路复用、良好的编码）</li><li>支持数据持久化</li><li>支持主从集群、分片集群</li><li>支持多语言客户端</li></ul><h1 id="二、Redis安装"><a href="#二、Redis安装" class="headerlink" title="二、Redis安装"></a>二、Redis安装</h1><h2 id="1-Mac安装Redis"><a href="#1-Mac安装Redis" class="headerlink" title="1.Mac安装Redis"></a>1.Mac安装Redis</h2><h3 id="1-1-安装redis"><a href="#1-1-安装redis" class="headerlink" title="1.1 安装redis"></a>1.1 安装redis</h3><pre class="line-numbers language-bash"><code class="language-bash">brew <span class="token function">install</span> redis<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><h3 id="1-2-启动Redis"><a href="#1-2-启动Redis" class="headerlink" title="1.2 启动Redis"></a>1.2 启动Redis</h3><pre class="line-numbers language-bash"><code class="language-bash"><span class="token comment" spellcheck="true"># 启动</span>brew services start redis<span class="token operator">==</span><span class="token operator">></span> Successfully started <span class="token variable"><span class="token variable">`</span>redis<span class="token variable">`</span></span> <span class="token punctuation">(</span>label: homebrew.mxcl.redis<span class="token punctuation">)</span><span class="token comment" spellcheck="true"># 关闭</span>brew services stop redis<span class="token comment" spellcheck="true"># 连接redis</span>redis-cli<span class="token comment" spellcheck="true"># 关闭连接，并退出</span>127.0.0.1:6379<span class="token operator">></span> <span class="token function">shutdown</span>not connected<span class="token operator">></span> quit<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="1-3-其它操作"><a href="#1-3-其它操作" class="headerlink" title="1.3 其它操作"></a>1.3 其它操作</h3><p><strong>远程连接Redis</strong></p><pre class="line-numbers language-bash"><code class="language-bash">redis-cli -h xxx.xx.xx.xx -p 6379 -a 123456<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><ul><li><code>-h xxx.xx.xx.xx</code>：指定要连接的redis节点的IP地址，默认是127.0.0.1</li><li><code>-p 6379</code>：指定要连接的redis节点的端口，默认是6379</li><li><code>-a 123456</code>：指定redis的访问密码</li></ul><p><strong>指定密码</strong></p><pre class="line-numbers language-bash"><code class="language-bash"><span class="token comment" spellcheck="true">#方法1</span><span class="token comment" spellcheck="true">#在配置文件中配置requirepass的密码（当redis重启后密码依然有效）。</span>requirepass foobared 修改成 <span class="token keyword">:</span> requirepass  123321<span class="token comment" spellcheck="true">#方法2（当redis重启后密码无效）。</span>127.0.0.1:6379<span class="token operator">></span>config <span class="token keyword">set</span> requirepass 123321 <span class="token comment" spellcheck="true">#设置密码</span>Ok127.0.0.1:6379<span class="token operator">></span>config get requirepass <span class="token comment" spellcheck="true">#查看密码</span>127.0.0.1:6379<span class="token operator">></span> auth 123321 <span class="token comment" spellcheck="true">#指定密码（登录时未指定密码可以用此命令制定密码）</span>Ok<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>心跳测试</strong></p><pre class="line-numbers language-bash"><code class="language-bash">127.0.0.1:6379<span class="token operator">></span> <span class="token function">ping</span> PONG<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><h2 id="2-Linux安装Redis"><a href="#2-Linux安装Redis" class="headerlink" title="2.Linux安装Redis"></a>2.Linux安装Redis</h2><hr><blockquote><p>本次安装Redis是基于Linux系统下安装的，因此需要一台Linux服务器或者虚拟机。如果您使用的是自己购买的服务器，请提前开放<code>6379</code>端口，避免后续出现的莫名其妙的错误！</p></blockquote><h3 id="2-1-安装依赖"><a href="#2-1-安装依赖" class="headerlink" title="2.1 安装依赖"></a>2.1 安装依赖</h3><hr><blockquote><p>Redis是基于C语言编写的，因此首先需要安装Redis所需要的gcc依赖</p></blockquote><pre class="line-numbers language-sh"><code class="language-sh">yum install -y gcc tcl<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p><strong>安装成功如下图所示：</strong></p><p><img src="https://img.jwt1399.top/img/202207161119201.png"></p><h3 id="2-2-安装Redis"><a href="#2-2-安装Redis" class="headerlink" title="2.2 安装Redis"></a>2.2 安装Redis</h3><hr><p><strong>将<code>redis-6.2.6.tar</code>上传至<code>/usr/local/src</code>目录</strong></p><p><img src="https://img.jwt1399.top/img/202207161119399.png"></p><p><strong>在xShell中<code>cd</code>到<code>/usr/local/src</code>目录执行以下命令进行解压操作</strong></p><pre class="line-numbers language-sh"><code class="language-sh">tar -xzf redis-6.2.6.tar.gz<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p><strong>解压成功后依次执行以下命令</strong></p><pre class="line-numbers language-sh"><code class="language-sh">cd redis-6.2.6makemake install<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p><strong>安装成功后打开&#x2F;usr&#x2F;local&#x2F;bin目录（该目录为Redis默认的安装目录）</strong></p><p><img src="https://img.jwt1399.top/img/202207161119072.png"></p><h3 id="2-3-启动Redis"><a href="#2-3-启动Redis" class="headerlink" title="2.3 启动Redis"></a>2.3 启动Redis</h3><blockquote><p>Redis的启动方式有很多种，例如：<strong>前台启动</strong>、<strong>后台启动</strong>、<strong>开机自启</strong></p></blockquote><h4 id="前台启动（不推荐）"><a href="#前台启动（不推荐）" class="headerlink" title="前台启动（不推荐）"></a>前台启动（不推荐）</h4><hr><blockquote><p><strong>这种启动属于前台启动，会阻塞整个会话窗口，窗口关闭或者按下<code>CTRL + C</code>则Redis停止。不推荐使用。</strong></p></blockquote><p><strong>安装完成后，在任意目录输入<code>redis-server</code>命令即可启动Redis</strong></p><pre class="line-numbers language-sh"><code class="language-sh">redis-server<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p><strong>启动成功如下图所示</strong></p><p><img src="https://img.jwt1399.top/img/202207161121008.png"></p><h4 id="后台启动（不推荐）"><a href="#后台启动（不推荐）" class="headerlink" title="后台启动（不推荐）"></a>后台启动（不推荐）</h4><hr><blockquote><p><strong>如果要让Redis以后台方式启动，则必须修改Redis配置文件，配置文件所在目录就是之前我们解压的安装包下</strong></p></blockquote><p><strong>因为我们要修改配置文件，因此我们需要先将原文件备份一份</strong></p><pre class="line-numbers language-sh"><code class="language-sh">cd /usr/local/src/redis-6.2.6<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><pre class="line-numbers language-sh"><code class="language-sh">cp redis.conf redis.conf.bck<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p><strong>然后修改<code>redis.conf</code>文件中的一些配置</strong></p><pre class="line-numbers language-sh"><code class="language-sh"># 允许访问的地址，默认是127.0.0.1，会导致只能在本地访问。修改为0.0.0.0则可以在任意IP访问，生产环境不要设置为0.0.0.0bind 0.0.0.0# 守护进程，修改为yes后即可后台运行daemonize yes # 密码，设置后访问Redis必须输入密码requirepass 1325<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>Redis其他常用配置</strong></p><pre class="line-numbers language-sh"><code class="language-sh"># 监听的端口port 6379# 工作目录，默认是当前目录，也就是运行redis-server时的命令，日志、持久化等文件会保存在这个目录dir .# 数据库数量，设置为1，代表只使用1个库，默认有16个库，编号0~15databases 1# 设置redis能够使用的最大内存maxmemory 512mb# 日志文件，默认为空，不记录日志，可以指定日志文件名logfile "redis.log"<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>启动Redis</strong></p><pre class="line-numbers language-sh"><code class="language-sh"># 进入redis安装目录 cd /usr/local/src/redis-6.2.6# 启动redis-server redis.conf<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p><strong>停止Redis服务</strong></p><pre class="line-numbers language-sh"><code class="language-sh"># 通过kill命令直接杀死进程kill -9 redis进程id<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><pre class="line-numbers language-sh"><code class="language-sh"># 利用redis-cli来执行 shutdown 命令，即可停止 Redis 服务，# 因为之前配置了密码，因此需要通过 -a 来指定密码redis-cli -a 132537 shutdown<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><h4 id="开机自启（推荐）"><a href="#开机自启（推荐）" class="headerlink" title="开机自启（推荐）"></a>开机自启（推荐）</h4><hr><blockquote><p><strong>我们也可以通过配置来实现开机自启</strong></p></blockquote><p><strong>首先，新建一个系统服务文件</strong></p><pre class="line-numbers language-sh"><code class="language-sh">vi /etc/systemd/system/redis.service<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p><strong>将以下命令粘贴进去</strong></p><pre class="line-numbers language-sh"><code class="language-sh">[Unit]Description=redis-serverAfter=network.target[Service]Type=forkingExecStart=/usr/local/bin/redis-server /usr/local/src/redis-6.2.6/redis.confPrivateTmp=true[Install]WantedBy=multi-user.target<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>然后重载系统服务</strong></p><pre class="line-numbers language-sh"><code class="language-sh">systemctl daemon-reload<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p><strong>现在，我们可以用下面这组命令来操作redis了</strong></p><pre class="line-numbers language-sh"><code class="language-sh"># 启动systemctl start redis# 停止systemctl stop redis# 重启systemctl restart redis# 查看状态systemctl status redis<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>执行下面的命令，可以让redis开机自启</strong></p><pre class="line-numbers language-sh"><code class="language-sh">systemctl enable redis<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><h1 id="三、Redis命令"><a href="#三、Redis命令" class="headerlink" title="三、Redis命令"></a>三、Redis命令</h1><p>通过Redis的中文文档学习：<a href="http://www.redis.cn/commands.html">http://www.redis.cn/commands.html</a></p><p>通过菜鸟教程官网来学习：<a href="https://www.runoob.com/redis/redis-keys.html">https://www.runoob.com/redis/redis-keys.html</a></p><p><strong>Redis是一个key-value的数据库，key一般是String类型，不过value的类型多种多样</strong></p><p><img src="https://img.jwt1399.top/img/202207161135146.png"></p><h2 id="1-通用命令"><a href="#1-通用命令" class="headerlink" title="1.通用命令"></a>1.通用命令</h2><table><thead><tr><th align="center">指令</th><th align="center">描述</th></tr></thead><tbody><tr><td align="center">keys</td><td align="center">查看符合模板的所有key，不建议在生产环境设备上使用</td></tr><tr><td align="center">del</td><td align="center">删除一个指定的key</td></tr><tr><td align="center">exists</td><td align="center">判断key是否存在</td></tr><tr><td align="center">expire</td><td align="center">给一个key设置有效期，有效期到期时该key会被自动删除</td></tr><tr><td align="center">ttl</td><td align="center">查看一个KEY的剩余有效期，-1表示永不过期，-2表示已过期</td></tr><tr><td align="center">type</td><td align="center">查看你的key是什么类型</td></tr><tr><td align="center">unlink</td><td align="center">根据value选择非阻塞删除</td></tr><tr><td align="center">select</td><td align="center">切换数据库</td></tr><tr><td align="center">dbsize</td><td align="center">查看当前数据库的key的数量</td></tr><tr><td align="center">flushdb</td><td align="center">清空当前库</td></tr><tr><td align="center">flushall</td><td align="center">通杀全部库</td></tr></tbody></table><p>可以通过<code>help [command] </code>可以查看一个命令的具体用法！</p><h2 id="2-基本类型"><a href="#2-基本类型" class="headerlink" title="2.基本类型"></a>2.基本类型</h2><h3 id="2-1-字符串-String"><a href="#2-1-字符串-String" class="headerlink" title="2.1 字符串-String"></a>2.1 字符串-String</h3><p>String类型是Redis最基本的数据类型，一个Redis中字符串value最多可以是512M</p><blockquote><p><strong>String的常见命令</strong></p></blockquote><table><thead><tr><th align="center">命令</th><th align="center">描述</th></tr></thead><tbody><tr><td align="center">set</td><td align="center">添加或者修改已经存在的一个String类型的键值对</td></tr><tr><td align="center">get</td><td align="center">根据key获取String类型的value</td></tr><tr><td align="center">mset</td><td align="center">批量添加多个String类型的键值对</td></tr><tr><td align="center">mget</td><td align="center">根据多个key获取多个String类型的value</td></tr><tr><td align="center">Strlen</td><td align="center">获得value的长度</td></tr><tr><td align="center">incr&#x2F;decr</td><td align="center">让一个整型的key自增&#x2F;自减1</td></tr><tr><td align="center">incrby&#x2F;decrby</td><td align="center">让一个整型的key自增&#x2F;自减，并指定步长，例如：incrby num 2 让num值自增2</td></tr><tr><td align="center">incrbyfloat</td><td align="center">让一个浮点类型的数字自增并指定步长</td></tr><tr><td align="center">setnx</td><td align="center">添加一个String类型的键值对，前提是这个key不存在，才执行</td></tr><tr><td align="center">setex</td><td align="center">添加一个String类型的键值对，并且指定有效期</td></tr><tr><td align="center">getrange</td><td align="center">获得value的范围，例如：getrange name 0 4</td></tr><tr><td align="center">setrange</td><td align="center">从指定位置覆盖key存储的value，例如：setrange name 0 jian</td></tr><tr><td align="center">getset</td><td align="center">以新换旧，设置了新值同时获得旧值。例如：getset name xiaojian</td></tr></tbody></table><p>数据结构：String的数据结构为简单动态字符串(Simple Dynamic String,缩写SDS)。是可以修改的字符串，内部结构实现上类似于Java的ArrayList，采用预分配冗余空间的方式来减少内存的频繁分配.</p><h3 id="2-2-哈希-Hash"><a href="#2-2-哈希-Hash" class="headerlink" title="2.2 哈希-Hash"></a>2.2 哈希-Hash</h3><p>hash是一个string类型的field和value的映射表，hash特别适合用于存储对象。类似Java里面的HashMap</p><p>每个 hash 可以存储 2<sup>32</sup> - 1 键值对（40多亿）。</p><p><img src="https://img.jwt1399.top/img/202207161523224.png"></p><blockquote><p>Hash的常见命令</p></blockquote><table><thead><tr><th align="center">命令</th><th align="center">描述</th></tr></thead><tbody><tr><td align="center">hset key field value</td><td align="center">添加或者修改hash类型key的field的值</td></tr><tr><td align="center">hget key field</td><td align="center">获取一个hash类型key的field的值</td></tr><tr><td align="center">hmset</td><td align="center">hmset 和 hset 效果相同 ，4.0之后hmset可以弃用了</td></tr><tr><td align="center">hmget</td><td align="center">批量获取多个hash类型key的field的值</td></tr><tr><td align="center">hgetall</td><td align="center">获取一个hash类型的key中的所有的field和value</td></tr><tr><td align="center">hkeys</td><td align="center">获取一个hash类型的key中的所有的field</td></tr><tr><td align="center">hvals</td><td align="center">获取一个hash类型的key中的所有的value</td></tr><tr><td align="center">hincrby</td><td align="center">让一个hash类型key的字段值自增并指定步长</td></tr><tr><td align="center">hsetnx</td><td align="center">添加一个hash类型的key的field值，前提是这个field不存在，否则不执行</td></tr></tbody></table><pre class="line-numbers language-bash"><code class="language-bash">127.0.0.1:6379<span class="token operator">></span> hset userkey name <span class="token string">"jack"</span> age 18 birth <span class="token string">"2004-07"</span><span class="token punctuation">(</span>integer<span class="token punctuation">)</span> 3127.0.0.1:6379<span class="token operator">></span> hgetall userkey1<span class="token punctuation">)</span> <span class="token string">"name"</span>2<span class="token punctuation">)</span> <span class="token string">"jack"</span>3<span class="token punctuation">)</span> <span class="token string">"age"</span>4<span class="token punctuation">)</span> <span class="token string">"18"</span>5<span class="token punctuation">)</span> <span class="token string">"birth"</span>6<span class="token punctuation">)</span> <span class="token string">"2004-07"</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><blockquote><p>数据结构</p></blockquote><p>Hash类型对应的数据结构是两种：ziplist（压缩列表），hashtable（哈希表）。当field-value长度较短且个数较少时，使用ziplist，否则使用hashtable。</p><h3 id="2-3-列表-List"><a href="#2-3-列表-List" class="headerlink" title="2.3 列表-List"></a>2.3 列表-List</h3><p>Redis中的List类型与Java中的LinkedList类似，可以看做是一个双向链表结构。既可以支持正向检索和也可以支持反向检索。</p><p>一个列表最多可以包含 2<sup>32</sup> - 1 个元素 (4294967295, 每个列表超过40亿个元素)。</p><p><strong>特征也与<code>LinkedList</code>类似：</strong></p><ul><li>有序</li><li>元素可以重复</li><li>插入和删除快</li><li>查询速度一般</li></ul><p>常用来存储一个有序数据，例如：朋友圈点赞列表，评论列表等。</p><p><img src="https://img.jwt1399.top/img/202207161524345.png"></p><blockquote><p>List的常见命令</p></blockquote><table><thead><tr><th align="center">命令</th><th align="center">描述</th></tr></thead><tbody><tr><td align="center">lpush key  element …</td><td align="center">向列表左侧插入一个或多个元素</td></tr><tr><td align="center">lpop key</td><td align="center">移除并返回列表左侧的第一个元素</td></tr><tr><td align="center">rpush key  element …</td><td align="center">向列表右侧插入一个或多个元素</td></tr><tr><td align="center">rpop key</td><td align="center">移除并返回列表右侧的第一个元素</td></tr><tr><td align="center">lrange key star end</td><td align="center">返回一段角标范围内的所有元素</td></tr><tr><td align="center">blpop和brpop</td><td align="center">与LPOP和RPOP类似，只不过在没有元素时等待指定时间，而不是直接返回nil</td></tr><tr><td align="center">rpoplpush &lt;key1&gt;&lt;key2&gt;</td><td align="center">从&lt;key1&gt;列表右边吐出一个值，插到&lt;key2&gt;列表左边。</td></tr><tr><td align="center">lindex  &lt;key&gt;&lt;index&gt;</td><td align="center">按照索引下标获得元素(从左到右)</td></tr><tr><td align="center">llen &lt;key&gt;</td><td align="center">获得列表长度</td></tr><tr><td align="center">lrem &lt;key&gt;&lt;n&gt;&lt;value&gt;</td><td align="center">从左边删除n个value</td></tr><tr><td align="center">lset &lt;key&gt;&lt;index&gt;&lt;value&gt;</td><td align="center">将列表key下标为index的值替换成value</td></tr></tbody></table><p><img src="https://img.jwt1399.top/img/202207161553022.gif"></p><blockquote><p>数据结构</p></blockquote><p>List的数据结构为快速链表quickList和压缩链表ziplist。</p><p>当列表元素较少的情况下会使用一块连续的内存存储，这个结构是ziplist。它将所有的元素紧挨着一起存储，分配的是一块连续的内存。</p><p>当数据量比较多的时候才会改成quicklist。因为普通的链表需要的附加指针空间太大，会比较浪费空间。比如这个列表里存的只是int类型的数据，结构上还需要两个额外的指针prev和next。</p><p>将链表和ziplist结合起来组成了quicklist。也就是将多个ziplist使用双向指针串起来使用。这样既满足了快速的插入删除性能，又不会出现太大的空间冗余。</p><blockquote><p><strong>思考问题</strong></p></blockquote><ul><li><p><strong>如何利用List结构模拟一个栈?</strong></p><ul><li>先进后出，入口和出口在同一边</li></ul></li><li><p><strong>如何利用List结构模拟一个队列?</strong></p><ul><li>先进先出，入口和出口在不同边</li></ul></li><li><p><strong>如何利用List结构模拟一个阻塞队列?</strong></p><ul><li>入口和出口在不同边</li><li>出队时采用BLPOP或BRPOP</li></ul></li></ul><h3 id="2-4-集合-Set"><a href="#2-4-集合-Set" class="headerlink" title="2.4 集合-Set"></a>2.4 集合-Set</h3><p>Redis的Set是string类型的无序集合。它底层其实是一个value为null的hash表，与Java中的HashSet类似，因此具备与HashSet类似的特征</p><ul><li>无序</li><li>元素不可重复</li><li>查找快</li><li>支持交集、并集、差集等功能</li></ul><blockquote><p><strong>Set的常见命令有</strong></p></blockquote><table><thead><tr><th align="center">命令</th><th align="center">描述</th></tr></thead><tbody><tr><td align="center">sadd key member …</td><td align="center">向set中添加一个或多个元素</td></tr><tr><td align="center">srem key member …</td><td align="center">移除set中的指定元素</td></tr><tr><td align="center">scard key</td><td align="center">返回set中元素的个数</td></tr><tr><td align="center">sismember key member</td><td align="center">判断一个元素是否存在于set中</td></tr><tr><td align="center">smembers key</td><td align="center">获取set中的所有元素</td></tr><tr><td align="center">sinter key1 key2 …</td><td align="center">求key1与key2的交集</td></tr><tr><td align="center">sdiff key1 key2 …</td><td align="center">求key1与key2的差集</td></tr><tr><td align="center">sunion key1 key2 ..</td><td align="center">求key1和key2的并集</td></tr><tr><td align="center">spop key</td><td align="center">随机从该集合中吐出一个值</td></tr><tr><td align="center">srandmember &lt;key&gt;&lt;n&gt;</td><td align="center">随机从该集合中取出n个值。不会从集合中删除</td></tr><tr><td align="center">smove &lt;source&gt;&lt;des&gt;value</td><td align="center">把集合中一个值从一个集合移动到另一个集合</td></tr></tbody></table><blockquote><p>交集、差集、并集图示</p></blockquote><p><img src="https://img.jwt1399.top/img/202207161610130.png"></p><blockquote><p>数据结构</p></blockquote><p>Set数据结构是dict字典，字典是用哈希表实现的。Java中HashSet的内部实现使用的是HashMap，只不过所有的value都指向同一个对象。Redis的set结构也是一样，它的内部也使用hash结构，所有的value都指向同一个内部值。</p><h3 id="2-5-有序集合-ZSet"><a href="#2-5-有序集合-ZSet" class="headerlink" title="2.5 有序集合-ZSet"></a>2.5 有序集合-ZSet</h3><p>Redis的ZSet（SortedSet）是一个可排序的set集合，与Java中的TreeSet有些类似，但底层数据结构却差别很大。SortedSet中的每一个元素都带有一个score属性，可以基于score属性对元素排序，底层的实现是一个跳表（SkipList）加 hash表。</p><p><strong>SortedSet具备下列特性：</strong></p><ul><li>可排序</li><li>元素不重复</li><li>查询速度快</li></ul><p>因为SortedSet的可排序特性，经常被用来实现排行榜这样的功能。</p><blockquote><p>SortedSet的常见命令</p></blockquote><table><thead><tr><th align="center">命令</th><th align="center">描述</th></tr></thead><tbody><tr><td align="center">zadd key score member</td><td align="center">添加一个或多个元素到sorted set ，如果已经存在则更新其score值</td></tr><tr><td align="center">zrem key member</td><td align="center">删除sorted set中的一个指定元素</td></tr><tr><td align="center">zscore key member</td><td align="center">获取sorted set中的指定元素的score值</td></tr><tr><td align="center">zrankkey member</td><td align="center">获取sorted set 中的指定元素的排名</td></tr><tr><td align="center">zcard key</td><td align="center">获取sorted set中的元素个数</td></tr><tr><td align="center">zcount key min max</td><td align="center">统计score值在给定范围内的所有元素的个数</td></tr><tr><td align="center">zincrby key increment member</td><td align="center">让sorted set中的指定元素自增，步长为指定的increment值</td></tr><tr><td align="center">zrange key min max</td><td align="center">按照score排序后，获取指定排名范围内的元素</td></tr><tr><td align="center">zrangebyscore key min max</td><td align="center">按照score排序后，获取指定score范围内的元素</td></tr><tr><td align="center">zdiff、zinter、zunion</td><td align="center">求差集、交集、并集</td></tr></tbody></table><p><strong>注意：所有的排名默认都是升序，如果要降序则在命令的Z后面添加<code>REV</code>即可</strong></p><blockquote><p>数据结构</p></blockquote><p>SortedSet(zset)是Redis提供的一个非常特别的数据结构，一方面它等价于Java的数据结构Map&lt;String, Double&gt;，可以给每一个元素value赋予一个权重score，另一方面它又类似于TreeSet，内部的元素会按照权重score进行排序，可以得到每个元素的名次，还可以通过score的范围来获取元素的列表。</p><p>zset底层使用了两个数据结构</p><p>（1）hash，hash的作用就是关联元素value和权重score，保障元素value的唯一性，可以通过元素value找到相应的score值。</p><p>（2）跳跃表，跳跃表的目的在于给元素value排序，根据score的范围获取元素列表。</p><h2 id="3-特殊类型"><a href="#3-特殊类型" class="headerlink" title="3.特殊类型"></a>3.特殊类型</h2><h3 id="3-1-Bitmaps"><a href="#3-1-Bitmaps" class="headerlink" title="3.1 Bitmaps"></a>3.1 Bitmaps</h3><blockquote><p>简介</p></blockquote><p>Redis 提供了 Bitmaps 可以实现对位的操作，可以把 Bitmaps 想象成一个以位为单位的数组， 数组的每个单元只能存储 0 和 1， 数组的下标在 Bitmaps 中叫做偏移量。</p><p><img src="https://img.jwt1399.top/img/202207171437184.png"></p><blockquote><p>Bitmaps的常见命令</p></blockquote><ul><li>setbit &lt;key&gt; &lt;offset&gt; &lt;value&gt; 设置Bitmaps中某个偏移量的值（0或1）</li><li>getbit &lt;key&gt; &lt;offset&gt; 获取Bitmaps中某个偏移量的值</li><li>bitcount &lt;key&gt; [start end] 统计字符串从start字节到end字节比特值为1的数量</li><li>bitop and(or&#x2F;not&#x2F;xor) &lt;destkey&gt; [key…] 可以做多个Bitmaps的and（交集） 、 or（并集） 、 not（非） 、 xor（异或） 操作并将结果保存在destkey中。</li><li>BITFIELD ：操作（查询、修改、自增）BitMap中bit数组中的指定位置（offset）的值</li></ul><pre class="line-numbers language-sql"><code class="language-sql">BITFIELD <span class="token keyword">key</span> GET encoding <span class="token keyword">offset</span><span class="token operator">|</span><span class="token punctuation">[</span>OVERFLOW WRAP<span class="token operator">|</span>SAT<span class="token operator">|</span>FAIL<span class="token punctuation">]</span> <span class="token keyword">SET</span> encoding <span class="token keyword">offset</span> <span class="token keyword">value</span><span class="token operator">|</span>INCRBY encoding <span class="token keyword">offset</span> increment <span class="token comment" spellcheck="true">#  GET查询 SET修改 INCRBY自增</span><span class="token comment" spellcheck="true">#  encoding 设置符号位和操作长度，u代表无符号，i代表有符号</span><span class="token comment" spellcheck="true">#  offset 偏移量，从第几位开始</span><span class="token comment" spellcheck="true"># 查询bit数组中从0位开始的2位，返回10进制</span>BITFIELD <span class="token keyword">key</span> GET u2 <span class="token number">0</span><span class="token comment" spellcheck="true"># 例如数据是11100，则返回3</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>BITFIELD_RO ：查询BitMap中bit数组，并以十进制形式返回，RO：READ_ONLY</li></ul><pre class="line-numbers language-sql"><code class="language-sql"><span class="token comment" spellcheck="true"># 功能跟BITFIELD的查询功能一样</span>BITFIELD_RO <span class="token keyword">key</span> GET encoding <span class="token keyword">offset</span> <span class="token punctuation">[</span>GET encoding <span class="token keyword">offset</span> <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">]</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><blockquote><p>实例1</p></blockquote><p>每个独立用户是否访问过网站，结果存放在Bitmaps中， 将访问的用户记做1， 没有访问的用户记做0， 用偏移量作为用户的id。 假设现在有20个用户，userid&#x3D;1， 6， 11， 15， 19的用户对网站进行了访问， 那么当前Bitmaps初始化结果如图</p><p><img src="https://img.jwt1399.top/img/202212041311883.png"></p><p>users:20220717代表2022-07-17这天的独立访问用户的Bitmaps</p><pre class="line-numbers language-bash"><code class="language-bash">127.0.0.1:6379<span class="token operator">></span> setbit users:20220717 1 1<span class="token punctuation">(</span>integer<span class="token punctuation">)</span> 0127.0.0.1:6379<span class="token operator">></span> setbit users:20220717 6 1<span class="token punctuation">(</span>integer<span class="token punctuation">)</span> 0127.0.0.1:6379<span class="token operator">></span> setbit users:20220717 11 1<span class="token punctuation">(</span>integer<span class="token punctuation">)</span> 0127.0.0.1:6379<span class="token operator">></span> setbit users:20220717 15 1<span class="token punctuation">(</span>integer<span class="token punctuation">)</span> 0127.0.0.1:6379<span class="token operator">></span> setbit users:20220717 19 1<span class="token punctuation">(</span>integer<span class="token punctuation">)</span> 0<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>获取id&#x3D;6，8的用户是否在2022-07-17这天访问过， 返回0说明没有访问过，返回1说明访问过</p><pre class="line-numbers language-bash"><code class="language-bash">127.0.0.1:6379<span class="token operator">></span> getbit users:20220717 6<span class="token punctuation">(</span>integer<span class="token punctuation">)</span> 1127.0.0.1:6379<span class="token operator">></span> getbit users:20220717 8<span class="token punctuation">(</span>integer<span class="token punctuation">)</span> 0<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>计算2022-07-17这天的独立访问用户数量</p><pre class="line-numbers language-bash"><code class="language-bash">127.0.0.1:6379<span class="token operator">></span> bitcount users:20220717<span class="token punctuation">(</span>integer<span class="token punctuation">)</span> 5<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>start和end代表起始和结束字节数， 下面计算用户id在第1个字节到第3个字节之间的独立访问用户数， 对应的用户id是11， 15， 19。</p><pre class="line-numbers language-bash"><code class="language-bash">127.0.0.1:6379<span class="token operator">></span> bitcount users:20220717 1 3<span class="token punctuation">(</span>integer<span class="token punctuation">)</span> 3<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><p>举例： K1 【01000001 01000000 00000000 00100001】，对应【0，1，2，3】</p><ul><li><p>bitcount K1 1 2 ： 统计下标1、2字节组中bit&#x3D;1的个数，即 01000000 00000000  –》1</p></li><li><p>bitcount K1 1 3 ： 统计下标1、3字节组中bit&#x3D;1的个数，即01000000 00000000 00100001 –》3</p></li><li><p>bitcount K1 0 -2 ： 统计下标0到下标倒数第2，字节组中bit&#x3D;1的个数，即01000001 01000000  00000000 –》3</p></li></ul><blockquote><p>实例2</p></blockquote><p>2022-07-02 日访问网站的userid&#x3D;1,2,5,9。</p><pre class="line-numbers language-sql"><code class="language-sql">setbit users:<span class="token number">20220702</span> <span class="token number">1</span> <span class="token number">1</span>setbit users:<span class="token number">20220702</span> <span class="token number">2</span> <span class="token number">1</span>setbit users:<span class="token number">20220702</span> <span class="token number">5</span> <span class="token number">1</span>setbit users:<span class="token number">20220702</span> <span class="token number">9</span> <span class="token number">1</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>2022-07-03 日访问网站的userid&#x3D;0,1,4,9。</p><pre class="line-numbers language-sql"><code class="language-sql">setbit users:<span class="token number">20220703</span> <span class="token number">0</span> <span class="token number">1</span>setbit users:<span class="token number">20220703</span> <span class="token number">1</span> <span class="token number">1</span>setbit users:<span class="token number">20220703</span> <span class="token number">4</span> <span class="token number">1</span>setbit users:<span class="token number">20220703</span> <span class="token number">9</span> <span class="token number">1</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>计算出两天都访问过网站的用户数量</p><pre class="line-numbers language-bash"><code class="language-bash">127.0.0.1:6379<span class="token operator">></span> bitop and users:and:20220702_03 users:20220702 users:20220703<span class="token punctuation">(</span>integer<span class="token punctuation">)</span> 2127.0.0.1:6379<span class="token operator">></span> bitcount users:and:20220702_03<span class="token punctuation">(</span>integer<span class="token punctuation">)</span> 2<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p>计算出任意一天都访问过网站的用户数量（例如月活跃就是类似这种） ， 可以使用or求并集</p><pre class="line-numbers language-bash"><code class="language-bash">127.0.0.1:6379<span class="token operator">></span> bitop or users:or:20220702_03 users:20220702 users:20220703<span class="token punctuation">(</span>integer<span class="token punctuation">)</span> 2127.0.0.1:6379<span class="token operator">></span> bitcount users:or:20220702_03<span class="token punctuation">(</span>integer<span class="token punctuation">)</span> 6<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><blockquote><p>Bitmaps与set对比</p></blockquote><p>假设网站有1亿用户， 每天独立访问的用户有5千万， 如果每天用集合类型和Bitmaps分别存储活跃用户可以得到表</p><table><thead><tr><th align="center">数据类型</th><th align="center">每个用户id占用空间</th><th align="center">需要存储的用户量</th><th align="center">全部内存量</th></tr></thead><tbody><tr><td align="center">集合类型</td><td align="center">64位</td><td align="center">5千万</td><td align="center">64位*5千万 &#x3D; 400MB</td></tr><tr><td align="center">Bitmaps</td><td align="center">1位</td><td align="center">1亿</td><td align="center">1位*1亿 &#x3D; 12.5MB</td></tr></tbody></table><p>很明显， 这种情况下使用Bitmaps能节省很多的内存空间， 尤其是随着时间推移节省的内存是非常可观的</p><table><thead><tr><th align="center">数据类型</th><th align="center">一天</th><th align="center">一个月</th><th align="center">一年</th></tr></thead><tbody><tr><td align="center">集合类型</td><td align="center">400MB</td><td align="center">12GB</td><td align="center">144GB</td></tr><tr><td align="center">Bitmaps</td><td align="center">12.5MB</td><td align="center">375MB</td><td align="center">4.5GB</td></tr></tbody></table><p>但Bitmaps并不是万金油， 假如该网站每天的独立访问用户很少， 例如只有10万（大量的僵尸用户） ， 那么两者的对比如下表所示， 很显然， 这时候使用Bitmaps就不太合适了， 因为基本上大部分位都是0。</p><table><thead><tr><th align="center">数据类型</th><th align="center">每个userid占用空间</th><th align="center">需要存储的用户量</th><th align="center">全部内存量</th></tr></thead><tbody><tr><td align="center">集合类型</td><td align="center">64位</td><td align="center">10万</td><td align="center">64位*10万 &#x3D; 800KB</td></tr><tr><td align="center">Bitmaps</td><td align="center">1位</td><td align="center">1亿</td><td align="center">1位* 1亿  &#x3D; 12.5MB</td></tr></tbody></table><h3 id="3-2-HyperLogLog"><a href="#3-2-HyperLogLog" class="headerlink" title="3.2 HyperLogLog"></a>3.2 HyperLogLog</h3><blockquote><p>简介</p></blockquote><p>在工作当中，我们经常会遇到与统计相关的功能需求，比如统计网站PV（PageView页面访问量）,可以使用Redis的incr、incrby轻松实现。但像UV（UniqueVisitor，独立访客）、独立IP数、搜索记录数等需要去重和计数的问题如何解决？这种求集合中不重复元素个数的问题称为<strong>基数问题</strong>。</p><p>解决基数问题有很多种方案：</p><ul><li><p>（1）数据存储在MySQL表中，使用distinct count计算不重复个数</p></li><li><p>（2）使用Redis提供的hash、set、bitmaps等数据结构来处理</p></li></ul><p>以上的方案结果精确，但随着数据不断增加，导致占用空间越来越大，当数据集非常大时是不切实际的。</p><p>能否能够降低一定的精度来平衡存储空间？Redis推出了HyperLogLog</p><ul><li><p>HyperLogLog 是用来做基数统计的算法，HyperLogLog 的优点是，在输入元素的数量或者体积非常非常大时，计算基数所需的空间总是固定的、并且是很小的。</p></li><li><p>在 Redis 里面，每个 HyperLogLog 键只需要花费 12KB 内存，就可以计算接近 2<sup>64</sup> 个不同元素的基数。这和计算基数时，元素越多耗费内存就越多的集合形成鲜明对比。</p></li><li><p>但是，因为 HyperLogLog 只会根据输入元素来计算基数，而不会储存输入元素本身，所以 HyperLogLog 不能像集合那样，返回输入的各个元素。</p></li></ul><p>什么是基数?</p><p>比如数据集 {1, 3, 5, 7, 5, 7, 8}， 那么这个数据集的基数集为 {1, 3, 5 ,7, 8}, 基数(不重复元素)为5。 基数估计就是在误差可接受的范围内，快速计算基数。</p><blockquote><p>HyperLogLog的常见命令</p></blockquote><ul><li>pfadd &lt;key&gt; &lt;element&gt; [element …]  添加指定元素到 HyperLogLog 中</li></ul><pre class="line-numbers language-bash"><code class="language-bash">127.0.0.1:6379<span class="token operator">></span> pfadd hll <span class="token string">"redis"</span><span class="token punctuation">(</span>integer<span class="token punctuation">)</span> 1127.0.0.1:6379<span class="token operator">></span> pfadd hll <span class="token string">"mysql"</span><span class="token punctuation">(</span>integer<span class="token punctuation">)</span> 1127.0.0.1:6379<span class="token operator">></span> pfadd hll <span class="token string">"redis"</span><span class="token punctuation">(</span>integer<span class="token punctuation">)</span> 0<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>pfcount &lt;key&gt; [key …] 计算HLL的近似基数，可以计算多个HLL，比如用HLL存储每天的UV，计算一周的UV可以使用7天的UV合并计算即可</li></ul><pre class="line-numbers language-bash"><code class="language-bash">127.0.0.1:6379<span class="token operator">></span> pfcount hll<span class="token punctuation">(</span>integer<span class="token punctuation">)</span> 2127.0.0.1:6379<span class="token operator">></span> pfadd hll2 <span class="token string">"mongodb"</span><span class="token punctuation">(</span>integer<span class="token punctuation">)</span> 1127.0.0.1:6379<span class="token operator">></span> pfadd hll2 <span class="token string">"redis"</span><span class="token punctuation">(</span>integer<span class="token punctuation">)</span> 1127.0.0.1:6379<span class="token operator">></span> pfcount hll hll2<span class="token punctuation">(</span>integer<span class="token punctuation">)</span> 3<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>pfmerge &lt;destkey&gt; &lt;sourcekey&gt; [sourcekey …] 将一个或多个HLL合并后的结果存储在另一个HLL中，比如每月活跃用户可以使用每天的活跃用户来合并计算可得</li></ul><pre class="line-numbers language-bash"><code class="language-bash">127.0.0.1:6379<span class="token operator">></span> pfmerge hllsum hll hll2OK127.0.0.1:6379<span class="token operator">></span> pfcount hllsum<span class="token punctuation">(</span>integer<span class="token punctuation">)</span> 3<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><h3 id="3-3-Geospatial"><a href="#3-3-Geospatial" class="headerlink" title="3.3 Geospatial"></a>3.3 Geospatial</h3><blockquote><p>简介</p></blockquote><p>Redis 3.2 中增加了对GEO类型的支持。GEO，Geographic，地理信息的缩写。该类型就是元素的2维坐标，在地图上就是经纬度。redis基于该类型，提供了经纬度设置，查询，范围查询，距离查询，经纬度Hash等常见操作。</p><blockquote><p>命令</p></blockquote><ul><li>geoadd &lt;key&gt; &lt; longitude&gt; &lt;latitude&gt; &lt;member&gt; [longitude latitude member…]  添加地理位置（经度，纬度，名称）</li></ul><pre class="line-numbers language-bash"><code class="language-bash">geoadd china:city 121.47 31.23 shanghaigeoadd china:city 106.50 29.53 chongqing 114.05 22.52 shenzhen 116.38 39.90 beijing<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><ul><li>geopos &lt;key&gt;&lt;member&gt; [member…] 获得指定地区的坐标值</li></ul><pre class="line-numbers language-bash"><code class="language-bash">127.0.0.1:6379<span class="token operator">></span> geopos china:city shanghai1<span class="token punctuation">)</span> 1<span class="token punctuation">)</span> <span class="token string">"121.47000163793563843"</span>   2<span class="token punctuation">)</span> <span class="token string">"31.22999903975783553"</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><ul><li>geodist &lt;key&gt; &lt;member1&gt;&lt;member2&gt; [m|km|ft|mi] 获取两个位置之间的直线距离<ul><li>m 表示单位为米[默认值]</li><li>km 表示单位为千米</li><li>ft 表示单位为英尺</li><li>mi 表示单位为英里</li></ul></li></ul><pre class="line-numbers language-bash"><code class="language-bash">127.0.0.1:6379<span class="token operator">></span> geodist china:city shanghai beijing km<span class="token string">"1068.1535"</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><ul><li>georadius &lt;key&gt; &lt;longitude&gt; &lt;latitude&gt; radius m|km|ft|mi  以给定的经纬度为中心，找出某一半径内的元素</li></ul><pre class="line-numbers language-bash"><code class="language-bash">127.0.0.1:6379<span class="token operator">></span> georadius china:city 110 30 1000 km1<span class="token punctuation">)</span> <span class="token string">"chongqing"</span>2<span class="token punctuation">)</span> <span class="token string">"shenzhen"</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><h1 id="四、Jedis"><a href="#四、Jedis" class="headerlink" title="四、Jedis"></a>四、Jedis</h1><h2 id="1-引入依赖"><a href="#1-引入依赖" class="headerlink" title="1.引入依赖"></a>1.引入依赖</h2><pre class="line-numbers language-xml"><code class="language-xml"><span class="token comment" spellcheck="true">&lt;!--引入Jedis依赖--></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>redis.clients<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>jedis<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>4.2.0<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span class="token comment" spellcheck="true">&lt;!--引入单元测试依赖--></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.junit.jupiter<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>junit-jupiter<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>5.8.2<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span>    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>scope</span><span class="token punctuation">></span></span>test<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>scope</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="2-与Redis建立连接"><a href="#2-与Redis建立连接" class="headerlink" title="2.与Redis建立连接"></a>2.与Redis建立连接</h2><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">JedisTest</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 获取连接</span>        Jedis jedis <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Jedis</span><span class="token punctuation">(</span><span class="token string">"127.0.0.1"</span><span class="token punctuation">,</span><span class="token number">6379</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 如果 Redis 服务设置了密码，需要下面这行，没有就不需要</span>        <span class="token comment" spellcheck="true">// jedis.auth("123456"); </span>        <span class="token comment" spellcheck="true">// 选择库（默认是下标为0的库）</span>        jedis<span class="token punctuation">.</span><span class="token function">select</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"连接成功"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">//查看服务是否运行</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"服务正在运行: "</span><span class="token operator">+</span>jedis<span class="token punctuation">.</span><span class="token function">ping</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        jedis<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="3-测试相关数据类型"><a href="#3-测试相关数据类型" class="headerlink" title="3.测试相关数据类型"></a>3.测试相关数据类型</h2><blockquote><p><strong>Jedis-API:  Key</strong></p></blockquote><pre class="line-numbers language-java"><code class="language-java">jedis<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span><span class="token string">"k1"</span><span class="token punctuation">,</span> <span class="token string">"v1"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>jedis<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span><span class="token string">"k2"</span><span class="token punctuation">,</span> <span class="token string">"v2"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>jedis<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span><span class="token string">"k3"</span><span class="token punctuation">,</span> <span class="token string">"v3"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>Set<span class="token operator">&lt;</span>String<span class="token operator">></span> keys <span class="token operator">=</span> jedis<span class="token punctuation">.</span><span class="token function">keys</span><span class="token punctuation">(</span><span class="token string">"*"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>keys<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">for</span> <span class="token punctuation">(</span>String key <span class="token operator">:</span> keys<span class="token punctuation">)</span> <span class="token punctuation">{</span>    System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>key<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span>System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>jedis<span class="token punctuation">.</span><span class="token function">exists</span><span class="token punctuation">(</span><span class="token string">"k1"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>jedis<span class="token punctuation">.</span><span class="token function">ttl</span><span class="token punctuation">(</span><span class="token string">"k1"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>                System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>jedis<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">"k1"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><blockquote><p><strong>Jedis-API:  String</strong></p></blockquote><pre class="line-numbers language-java"><code class="language-java">jedis<span class="token punctuation">.</span><span class="token function">mset</span><span class="token punctuation">(</span><span class="token string">"str1"</span><span class="token punctuation">,</span><span class="token string">"v1"</span><span class="token punctuation">,</span><span class="token string">"str2"</span><span class="token punctuation">,</span><span class="token string">"v2"</span><span class="token punctuation">,</span><span class="token string">"str3"</span><span class="token punctuation">,</span><span class="token string">"v3"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>jedis<span class="token punctuation">.</span><span class="token function">mget</span><span class="token punctuation">(</span><span class="token string">"str1"</span><span class="token punctuation">,</span><span class="token string">"str2"</span><span class="token punctuation">,</span><span class="token string">"str3"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><blockquote><p><strong>Jedis-API:  List</strong></p></blockquote><pre class="line-numbers language-java"><code class="language-java">List<span class="token operator">&lt;</span>String<span class="token operator">></span> list <span class="token operator">=</span> jedis<span class="token punctuation">.</span><span class="token function">lrange</span><span class="token punctuation">(</span><span class="token string">"mylist"</span><span class="token punctuation">,</span><span class="token number">0</span><span class="token punctuation">,</span><span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">for</span> <span class="token punctuation">(</span>String element <span class="token operator">:</span> list<span class="token punctuation">)</span> <span class="token punctuation">{</span>    System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>element<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><blockquote><p><strong>Jedis-API:  set</strong></p></blockquote><pre class="line-numbers language-java"><code class="language-java">jedis<span class="token punctuation">.</span><span class="token function">sadd</span><span class="token punctuation">(</span><span class="token string">"orders"</span><span class="token punctuation">,</span> <span class="token string">"order01"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>jedis<span class="token punctuation">.</span><span class="token function">sadd</span><span class="token punctuation">(</span><span class="token string">"orders"</span><span class="token punctuation">,</span> <span class="token string">"order02"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>jedis<span class="token punctuation">.</span><span class="token function">sadd</span><span class="token punctuation">(</span><span class="token string">"orders"</span><span class="token punctuation">,</span> <span class="token string">"order03"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>jedis<span class="token punctuation">.</span><span class="token function">sadd</span><span class="token punctuation">(</span><span class="token string">"orders"</span><span class="token punctuation">,</span> <span class="token string">"order04"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>Set<span class="token operator">&lt;</span>String<span class="token operator">></span> smembers <span class="token operator">=</span> jedis<span class="token punctuation">.</span><span class="token function">smembers</span><span class="token punctuation">(</span><span class="token string">"orders"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">for</span> <span class="token punctuation">(</span>String order <span class="token operator">:</span> smembers<span class="token punctuation">)</span> <span class="token punctuation">{</span>System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>order<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span>jedis<span class="token punctuation">.</span><span class="token function">srem</span><span class="token punctuation">(</span><span class="token string">"orders"</span><span class="token punctuation">,</span> <span class="token string">"order02"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><blockquote><p><strong>Jedis-API:  hash</strong></p></blockquote><pre class="line-numbers language-java"><code class="language-java">jedis<span class="token punctuation">.</span><span class="token function">hset</span><span class="token punctuation">(</span><span class="token string">"hash1"</span><span class="token punctuation">,</span><span class="token string">"userName"</span><span class="token punctuation">,</span><span class="token string">"lisi"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>jedis<span class="token punctuation">.</span><span class="token function">hget</span><span class="token punctuation">(</span><span class="token string">"hash1"</span><span class="token punctuation">,</span><span class="token string">"userName"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>Map<span class="token operator">&lt;</span>String<span class="token punctuation">,</span>String<span class="token operator">></span> map <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">HashMap</span><span class="token operator">&lt;</span>String<span class="token punctuation">,</span>String<span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>map<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token string">"telphone"</span><span class="token punctuation">,</span><span class="token string">"13735679666"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>map<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token string">"address"</span><span class="token punctuation">,</span><span class="token string">"beijing"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>map<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token string">"email"</span><span class="token punctuation">,</span><span class="token string">"abc@163.com"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>jedis<span class="token punctuation">.</span><span class="token function">hmset</span><span class="token punctuation">(</span><span class="token string">"hash2"</span><span class="token punctuation">,</span>map<span class="token punctuation">)</span><span class="token punctuation">;</span>List<span class="token operator">&lt;</span>String<span class="token operator">></span> result <span class="token operator">=</span> jedis<span class="token punctuation">.</span><span class="token function">hmget</span><span class="token punctuation">(</span><span class="token string">"hash2"</span><span class="token punctuation">,</span> <span class="token string">"telphone"</span><span class="token punctuation">,</span><span class="token string">"email"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">for</span> <span class="token punctuation">(</span>String element <span class="token operator">:</span> result<span class="token punctuation">)</span> <span class="token punctuation">{</span>    System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>element<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><blockquote><p><strong>Jedis-API:  zset</strong></p></blockquote><pre class="line-numbers language-java"><code class="language-java">jedis<span class="token punctuation">.</span><span class="token function">zadd</span><span class="token punctuation">(</span><span class="token string">"zset01"</span><span class="token punctuation">,</span> <span class="token number">100d</span><span class="token punctuation">,</span> <span class="token string">"z3"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>jedis<span class="token punctuation">.</span><span class="token function">zadd</span><span class="token punctuation">(</span><span class="token string">"zset01"</span><span class="token punctuation">,</span> <span class="token number">90d</span><span class="token punctuation">,</span> <span class="token string">"l4"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>jedis<span class="token punctuation">.</span><span class="token function">zadd</span><span class="token punctuation">(</span><span class="token string">"zset01"</span><span class="token punctuation">,</span> <span class="token number">80d</span><span class="token punctuation">,</span> <span class="token string">"w5"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>jedis<span class="token punctuation">.</span><span class="token function">zadd</span><span class="token punctuation">(</span><span class="token string">"zset01"</span><span class="token punctuation">,</span> <span class="token number">70d</span><span class="token punctuation">,</span> <span class="token string">"z6"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>List<span class="token operator">&lt;</span>String<span class="token operator">></span> zrange <span class="token operator">=</span> jedis<span class="token punctuation">.</span><span class="token function">zrange</span><span class="token punctuation">(</span><span class="token string">"zset01"</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">for</span> <span class="token punctuation">(</span>String e <span class="token operator">:</span> zrange<span class="token punctuation">)</span> <span class="token punctuation">{</span>  System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="4-Jedis连接池"><a href="#4-Jedis连接池" class="headerlink" title="4.Jedis连接池"></a>4.Jedis连接池</h2><hr><blockquote><p><strong>Jedis本身是线程不安全的，并且频繁的创建和销毁连接会有性能损耗，因此推荐大家使用Jedis连接池代替Jedis的直连方式</strong></p></blockquote><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">JedisConnectionFactory</span> <span class="token punctuation">{</span>    <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> JedisPool jedisPool<span class="token punctuation">;</span>    <span class="token keyword">static</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">//配置连接池</span>        JedisPoolConfig jedisPoolConfig <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">JedisPoolConfig</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        jedisPoolConfig<span class="token punctuation">.</span><span class="token function">setMaxTotal</span><span class="token punctuation">(</span><span class="token number">8</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        jedisPoolConfig<span class="token punctuation">.</span><span class="token function">setMaxIdle</span><span class="token punctuation">(</span><span class="token number">8</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        jedisPoolConfig<span class="token punctuation">.</span><span class="token function">setMinIdle</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        jedisPoolConfig<span class="token punctuation">.</span><span class="token function">setMaxWaitMillis</span><span class="token punctuation">(</span><span class="token number">200</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">//创建连接池对象</span>        jedisPool <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">JedisPool</span><span class="token punctuation">(</span>jedisPoolConfig<span class="token punctuation">,</span><span class="token string">"127.0.0.1"</span><span class="token punctuation">,</span><span class="token number">6379</span><span class="token punctuation">,</span><span class="token number">1000</span><span class="token punctuation">,</span><span class="token string">"132537"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> Jedis <span class="token function">getJedis</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>       <span class="token keyword">return</span> jedisPool<span class="token punctuation">.</span><span class="token function">getResource</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="5、实例—手机验证码"><a href="#5、实例—手机验证码" class="headerlink" title="5、实例—手机验证码"></a>5、实例—手机验证码</h2><blockquote><p>要求：<br>1、输入手机号，点击发送后随机生成6位数字码，2分钟有效<br>2、输入验证码，点击验证，返回成功或失败<br>3、每个手机号每天只能输入3次</p></blockquote><p>思路：</p><ol><li>生成随机6位数字验证码：Random</li><li>验证码在2分钟内有效：把验证码放到redis里面，设置过期时间120秒</li><li>判断验证码是否一致：从redis获取验证码和输入的验证码进行比较</li><li>每个手机每天只能发送3次验证码：incr每次发送后+1，大于2的时候，提交不能发送</li></ol><p><strong>生成六位的验证码：</strong></p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">//1.生成6位数字验证码</span><span class="token keyword">public</span> <span class="token keyword">static</span> String <span class="token function">getCode</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    Random random <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Random</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    String code <span class="token operator">=</span> <span class="token string">""</span><span class="token punctuation">;</span>    <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i<span class="token operator">=</span><span class="token number">0</span><span class="token punctuation">;</span>i<span class="token operator">&lt;</span><span class="token number">6</span><span class="token punctuation">;</span>i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token keyword">int</span> rand <span class="token operator">=</span> random<span class="token punctuation">.</span><span class="token function">nextInt</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        code <span class="token operator">+=</span> rand<span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token keyword">return</span> code<span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>验证码只能发送三次：</strong></p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">//2 每个手机每天只能发送三次，验证码放到redis中，设置过期时间120</span><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">verifyCode</span><span class="token punctuation">(</span>String phone<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">//连接redis</span>    Jedis jedis <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Jedis</span><span class="token punctuation">(</span><span class="token string">"127.0.0.1"</span><span class="token punctuation">,</span><span class="token number">6379</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">//拼接key</span>    <span class="token comment" spellcheck="true">//手机发送次数key</span>    String countKey <span class="token operator">=</span> <span class="token string">"VerifyCode"</span><span class="token operator">+</span>phone<span class="token operator">+</span><span class="token string">":count"</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">//验证码key</span>    String codeKey <span class="token operator">=</span> <span class="token string">"VerifyCode"</span><span class="token operator">+</span>phone<span class="token operator">+</span><span class="token string">":code"</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">//每个手机每天只能发送三次</span>    String count <span class="token operator">=</span> jedis<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>countKey<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token keyword">if</span><span class="token punctuation">(</span>count <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">//没有发送次数，第一次发送</span>        <span class="token comment" spellcheck="true">//设置发送次数是1</span>        jedis<span class="token punctuation">.</span><span class="token function">setex</span><span class="token punctuation">(</span>countKey<span class="token punctuation">,</span><span class="token number">24</span><span class="token operator">*</span><span class="token number">60</span><span class="token operator">*</span><span class="token number">60</span><span class="token punctuation">,</span><span class="token string">"1"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span><span class="token punctuation">(</span>Integer<span class="token punctuation">.</span><span class="token function">parseInt</span><span class="token punctuation">(</span>count<span class="token punctuation">)</span><span class="token operator">&lt;=</span><span class="token number">2</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">//发送次数+1</span>        jedis<span class="token punctuation">.</span><span class="token function">incr</span><span class="token punctuation">(</span>countKey<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span><span class="token punctuation">(</span>Integer<span class="token punctuation">.</span><span class="token function">parseInt</span><span class="token punctuation">(</span>count<span class="token punctuation">)</span><span class="token operator">></span><span class="token number">2</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">//发送三次，不能再发送</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"今天发送次数已经超过三次"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        jedis<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">//发送验证码放到redis里面</span>    String vcode <span class="token operator">=</span> <span class="token function">getCode</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    jedis<span class="token punctuation">.</span><span class="token function">setex</span><span class="token punctuation">(</span>codeKey<span class="token punctuation">,</span><span class="token number">120</span><span class="token punctuation">,</span>vcode<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//120秒</span>    jedis<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>判断验证码是否一致：</strong></p><pre class="line-numbers language-java"><code class="language-java"><span class="token comment" spellcheck="true">//3 验证码校验</span><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">judgeCode</span><span class="token punctuation">(</span>String phone<span class="token punctuation">,</span>String code<span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">//从redis获取验证码</span>    Jedis jedis <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Jedis</span><span class="token punctuation">(</span><span class="token string">"127.0.0.1"</span><span class="token punctuation">,</span><span class="token number">6379</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">//验证码key</span>    String codeKey <span class="token operator">=</span> <span class="token string">"VerifyCode"</span><span class="token operator">+</span>phone<span class="token operator">+</span><span class="token string">":code"</span><span class="token punctuation">;</span>    String redisCode <span class="token operator">=</span> jedis<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>codeKey<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">//判断</span>    <span class="token keyword">if</span><span class="token punctuation">(</span>redisCode<span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span>code<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"成功"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token keyword">else</span> <span class="token punctuation">{</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"失败"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    jedis<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>完整功能代码展示</strong></p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">PhoneCode</span> <span class="token punctuation">{</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">//模拟验证码发送</span>        <span class="token function">verifyCode</span><span class="token punctuation">(</span><span class="token string">"13678765435"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">//模拟验证码校验</span>        <span class="token comment" spellcheck="true">//judgeCode("13678765435","217173");</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">//3 验证码校验</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">judgeCode</span><span class="token punctuation">(</span>String phone<span class="token punctuation">,</span>String code<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">//从redis获取验证码</span>        Jedis jedis <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Jedis</span><span class="token punctuation">(</span><span class="token string">"127.0.0.1"</span><span class="token punctuation">,</span><span class="token number">6379</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">//验证码key</span>        String codeKey <span class="token operator">=</span> <span class="token string">"VerifyCode"</span><span class="token operator">+</span>phone<span class="token operator">+</span><span class="token string">":code"</span><span class="token punctuation">;</span>        String redisCode <span class="token operator">=</span> jedis<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>codeKey<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">//判断</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>redisCode<span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span>code<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"成功"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span><span class="token keyword">else</span> <span class="token punctuation">{</span>            System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"失败"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        jedis<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">//2 每个手机每天只能发送三次，验证码放到redis中，设置过期时间120</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">verifyCode</span><span class="token punctuation">(</span>String phone<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">//连接redis</span>        Jedis jedis <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Jedis</span><span class="token punctuation">(</span><span class="token string">"127.0.0.1"</span><span class="token punctuation">,</span><span class="token number">6379</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">//拼接key</span>        <span class="token comment" spellcheck="true">//手机发送次数key</span>        String countKey <span class="token operator">=</span> <span class="token string">"VerifyCode"</span><span class="token operator">+</span>phone<span class="token operator">+</span><span class="token string">":count"</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">//验证码key</span>        String codeKey <span class="token operator">=</span> <span class="token string">"VerifyCode"</span><span class="token operator">+</span>phone<span class="token operator">+</span><span class="token string">":code"</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">//每个手机每天只能发送三次</span>        String count <span class="token operator">=</span> jedis<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>countKey<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>count <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">//没有发送次数，第一次发送</span>            <span class="token comment" spellcheck="true">//设置发送次数是1</span>            jedis<span class="token punctuation">.</span><span class="token function">setex</span><span class="token punctuation">(</span>countKey<span class="token punctuation">,</span><span class="token number">24</span><span class="token operator">*</span><span class="token number">60</span><span class="token operator">*</span><span class="token number">60</span><span class="token punctuation">,</span><span class="token string">"1"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//有效期1天</span>        <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span><span class="token punctuation">(</span>Integer<span class="token punctuation">.</span><span class="token function">parseInt</span><span class="token punctuation">(</span>count<span class="token punctuation">)</span><span class="token operator">&lt;=</span><span class="token number">2</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">//发送次数+1</span>            jedis<span class="token punctuation">.</span><span class="token function">incr</span><span class="token punctuation">(</span>countKey<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span><span class="token punctuation">(</span>Integer<span class="token punctuation">.</span><span class="token function">parseInt</span><span class="token punctuation">(</span>count<span class="token punctuation">)</span><span class="token operator">></span><span class="token number">2</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">//发送三次，不能再发送</span>            System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"今天发送次数已经超过三次"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            jedis<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token keyword">return</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//超过三次之后就会自动退出不会再发送了，不添加这一行，即使显示发送次数，但还会有验证码还是会改变</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">//发送验证码放到redis里面</span>        String vcode <span class="token operator">=</span> <span class="token function">getCode</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//调用生成的验证码</span>        jedis<span class="token punctuation">.</span><span class="token function">setex</span><span class="token punctuation">(</span>codeKey<span class="token punctuation">,</span><span class="token number">120</span><span class="token punctuation">,</span>vcode<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//设置生成的验证码只有120秒的时间</span>        jedis<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span>    <span class="token comment" spellcheck="true">//1 生成6位数字验证码，code是验证码</span>    <span class="token keyword">public</span> <span class="token keyword">static</span> String <span class="token function">getCode</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        Random random <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Random</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        String code <span class="token operator">=</span> <span class="token string">""</span><span class="token punctuation">;</span>        <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i<span class="token operator">=</span><span class="token number">0</span><span class="token punctuation">;</span>i<span class="token operator">&lt;</span><span class="token number">6</span><span class="token punctuation">;</span>i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token keyword">int</span> rand <span class="token operator">=</span> random<span class="token punctuation">.</span><span class="token function">nextInt</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            code <span class="token operator">+=</span> rand<span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token keyword">return</span> code<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h1 id="五、Redis与Spring-Boot整合"><a href="#五、Redis与Spring-Boot整合" class="headerlink" title="五、Redis与Spring Boot整合"></a>五、Redis与Spring Boot整合</h1><h2 id="1、引入redis相关依赖"><a href="#1、引入redis相关依赖" class="headerlink" title="1、引入redis相关依赖"></a>1、引入redis相关依赖</h2><pre class="line-numbers language-xml"><code class="language-xml"><span class="token comment" spellcheck="true">&lt;!-- redis --></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework.boot<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-boot-starter-data-redis<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span class="token comment" spellcheck="true">&lt;!--连接池依赖--></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.apache.commons<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>commons-pool2<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>2.6.0<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="2、编写配置文件"><a href="#2、编写配置文件" class="headerlink" title="2、编写配置文件"></a>2、编写配置文件</h2><p>application.yml 版本</p><pre class="line-numbers language-yaml"><code class="language-yaml"><span class="token key atrule">spring</span><span class="token punctuation">:</span>  <span class="token key atrule">redis</span><span class="token punctuation">:</span>    <span class="token key atrule">host</span><span class="token punctuation">:</span> 127.0.0.1 <span class="token comment" spellcheck="true">#指定redis所在的host</span>    <span class="token key atrule">port</span><span class="token punctuation">:</span> <span class="token number">6379  </span><span class="token comment" spellcheck="true">#指定redis的端口</span>    <span class="token key atrule">password</span><span class="token punctuation">:</span> <span class="token number">123456  </span><span class="token comment" spellcheck="true">#设置redis密码</span>    <span class="token key atrule">lettuce</span><span class="token punctuation">:</span>      <span class="token key atrule">pool</span><span class="token punctuation">:</span>        <span class="token key atrule">max-active</span><span class="token punctuation">:</span> <span class="token number">8 </span><span class="token comment" spellcheck="true">#最大连接数</span>        <span class="token key atrule">max-idle</span><span class="token punctuation">:</span> <span class="token number">8 </span><span class="token comment" spellcheck="true">#最大空闲数</span>        <span class="token key atrule">min-idle</span><span class="token punctuation">:</span> <span class="token number">0 </span><span class="token comment" spellcheck="true">#最小空闲数</span>        <span class="token key atrule">max-wait</span><span class="token punctuation">:</span> 100ms <span class="token comment" spellcheck="true">#连接等待时间</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>application.properties 版本</p><pre class="line-numbers language-properties"><code class="language-properties"><span class="token comment" spellcheck="true">#Redis服务器地址</span><span class="token attr-name">spring.redis.host</span><span class="token punctuation">=</span><span class="token attr-value">127.0.0.1</span><span class="token comment" spellcheck="true">#Redis服务器连接端口</span><span class="token attr-name">spring.redis.port</span><span class="token punctuation">=</span><span class="token attr-value">6379</span><span class="token comment" spellcheck="true">#Redis数据库索引（默认为0）</span><span class="token attr-name">spring.redis.database</span><span class="token punctuation">=</span> <span class="token attr-value">0</span><span class="token comment" spellcheck="true">#连接超时时间（毫秒）</span><span class="token attr-name">spring.redis.timeout</span><span class="token punctuation">=</span><span class="token attr-value">1800000</span><span class="token comment" spellcheck="true">#连接池最大连接数（使用负值表示没有限制）</span><span class="token attr-name">spring.redis.lettuce.pool.max-active</span><span class="token punctuation">=</span><span class="token attr-value">20</span><span class="token comment" spellcheck="true">#最大阻塞等待时间(负数表示没限制)</span><span class="token attr-name">spring.redis.lettuce.pool.max-wait</span><span class="token punctuation">=</span><span class="token attr-value">-1</span><span class="token comment" spellcheck="true">#连接池中的最大空闲连接</span><span class="token attr-name">spring.redis.lettuce.pool.max-idle</span><span class="token punctuation">=</span><span class="token attr-value">5</span><span class="token comment" spellcheck="true">#连接池中的最小空闲连接</span><span class="token attr-name">spring.redis.lettuce.pool.min-idle</span><span class="token punctuation">=</span><span class="token attr-value">0</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="3、编写测试类"><a href="#3、编写测试类" class="headerlink" title="3、编写测试类"></a><strong>3、编写测试类</strong></h2><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@SpringBootTest</span><span class="token keyword">class</span> <span class="token class-name">SpringRedisApplicationTests</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Resource</span>    <span class="token keyword">private</span> RedisTemplate redisTemplate<span class="token punctuation">;</span>    <span class="token annotation punctuation">@Test</span>    <span class="token keyword">void</span> <span class="token function">testString</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 1.通过RedisTemplate获取操作String类型的ValueOperations对象</span>        ValueOperations ops <span class="token operator">=</span> redisTemplate<span class="token punctuation">.</span><span class="token function">opsForValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 2.插入一条数据</span>        ops<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span><span class="token string">"name"</span><span class="token punctuation">,</span><span class="token string">"jianjian"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 3.获取数据</span>        String name <span class="token operator">=</span> <span class="token punctuation">(</span>String<span class="token punctuation">)</span> ops<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">"name"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"name = "</span> <span class="token operator">+</span> name<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="4、RedisSerializer配置"><a href="#4、RedisSerializer配置" class="headerlink" title="4、RedisSerializer配置"></a>4、RedisSerializer配置</h2><hr><blockquote><p><strong>RedisTemplate可以接收任意Object作为值写入Redis，只不过写入前会把Object序列化为字节形式，<code>默认是采用JDK序列化</code>，得到的结果是这样的</strong></p></blockquote><p><img src="https://img.jwt1399.top/img/202207181557941.png"></p><p><strong>缺点：</strong></p><ul><li>可读性差</li><li>内存占用较大</li></ul><blockquote><p><strong>那么如何解决以上的问题呢？我们可以通过自定义RedisTemplate序列化的方式来解决。</strong></p></blockquote><p><strong>编写一个配置类<code>RedisConfig</code></strong></p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Configuration</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">RedisConfig</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Bean</span>    <span class="token keyword">public</span> RedisTemplate<span class="token operator">&lt;</span>String<span class="token punctuation">,</span>Object<span class="token operator">></span> <span class="token function">redisTemplate</span><span class="token punctuation">(</span>RedisConnectionFactory factory<span class="token punctuation">)</span><span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 1.创建RedisTemplate对象</span>        RedisTemplate<span class="token operator">&lt;</span>String <span class="token punctuation">,</span>Object<span class="token operator">></span> redisTemplate <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">RedisTemplate</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 2.设置连接工厂</span>        redisTemplate<span class="token punctuation">.</span><span class="token function">setConnectionFactory</span><span class="token punctuation">(</span>factory<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 3.创建序列化对象</span>        StringRedisSerializer stringRedisSerializer <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">StringRedisSerializer</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">GenericJackson2JsonRedisSerializer</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 4.设置key和hashKey采用String的序列化方式</span>        redisTemplate<span class="token punctuation">.</span><span class="token function">setKeySerializer</span><span class="token punctuation">(</span>stringRedisSerializer<span class="token punctuation">)</span><span class="token punctuation">;</span>        redisTemplate<span class="token punctuation">.</span><span class="token function">setHashKeySerializer</span><span class="token punctuation">(</span>stringRedisSerializer<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 5.设置value和hashValue采用json的序列化方式</span>        redisTemplate<span class="token punctuation">.</span><span class="token function">setValueSerializer</span><span class="token punctuation">(</span>genericJackson2JsonRedisSerializer<span class="token punctuation">)</span><span class="token punctuation">;</span>        redisTemplate<span class="token punctuation">.</span><span class="token function">setHashValueSerializer</span><span class="token punctuation">(</span>genericJackson2JsonRedisSerializer<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token keyword">return</span> redisTemplate<span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>此时我们已经将RedisTemplate的key设置为<code>String序列化</code>，value设置为<code>Json序列化</code>的方式，再来执行方法测试</strong></p><p><img src="https://img.jwt1399.top/img/202207181557925.png"></p><p><strong>由于我们设置的value序列化方式是Json的，因此我们可以直接向redis中插入一个对象</strong></p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@Test</span><span class="token keyword">void</span> <span class="token function">testSaveUser</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    redisTemplate<span class="token punctuation">.</span><span class="token function">opsForValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span><span class="token string">"user:100"</span><span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name">User</span><span class="token punctuation">(</span><span class="token string">"Vz"</span><span class="token punctuation">,</span> <span class="token number">21</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    User user <span class="token operator">=</span> <span class="token punctuation">(</span>User<span class="token punctuation">)</span> redisTemplate<span class="token punctuation">.</span><span class="token function">opsForValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">"user:100"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"User = "</span> <span class="token operator">+</span> user<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><img src="https://img.jwt1399.top/img/202207181609335.png"></p><p>尽管Json序列化可以满足我们的需求，但是依旧存在一些问题。</p><p>如上图所示，为了在反序列化时知道对象的类型，JSON序列化器会将类的class类型写入json结果中，存入Redis，会带来额外的内存开销。</p><p>那么我们如何解决这个问题呢？我们可以通过下文的<code>StringRedisTemplate</code>来解决这个问题。</p><h2 id="5、StringRedisTemplate配置"><a href="#5、StringRedisTemplate配置" class="headerlink" title="5、StringRedisTemplate配置"></a>5、StringRedisTemplate配置</h2><blockquote><p><strong>为了节省内存空间，我们并不会使用JSON序列化器来处理value，而是统一使用String序列化器，要求只能存储String类型的key和value。当需要存储Java对象时，手动完成对象的序列化和反序列化。</strong></p></blockquote><p><img src="https://img.jwt1399.top/img/202207181939756.png"></p><blockquote><p><strong>Spring默认提供了一个StringRedisTemplate类，它的key和value的序列化方式默认就是String方式。省去了我们自定义RedisTemplate的过程</strong></p></blockquote><p><strong>编写一个测试类使用StringRedisTemplate来执行以下方法</strong></p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@SpringBootTest</span><span class="token keyword">class</span> <span class="token class-name">RedisStringTemplateTest</span> <span class="token punctuation">{</span>    <span class="token annotation punctuation">@Resource</span>    <span class="token keyword">private</span> StringRedisTemplate stringRedisTemplate<span class="token punctuation">;</span>    <span class="token annotation punctuation">@Test</span>    <span class="token keyword">void</span> <span class="token function">testSaveUser</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">throws</span> JsonProcessingException <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 1.创建一个Json序列化对象</span>        ObjectMapper objectMapper <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ObjectMapper</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 2.将要存入的对象通过Json序列化对象转换为字符串</span>        String userJson1 <span class="token operator">=</span> objectMapper<span class="token punctuation">.</span><span class="token function">writeValueAsString</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">User</span><span class="token punctuation">(</span><span class="token string">"Vz"</span><span class="token punctuation">,</span> <span class="token number">21</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 3.通过StringRedisTemplate将数据存入redis</span>        stringRedisTemplate<span class="token punctuation">.</span><span class="token function">opsForValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span><span class="token string">"user:100"</span><span class="token punctuation">,</span>userJson1<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 4.通过key取出value</span>        String userJson2 <span class="token operator">=</span> stringRedisTemplate<span class="token punctuation">.</span><span class="token function">opsForValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">"user:100"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 5.由于取出的值是String类型的Json字符串，因此我们需要通过Json序列化对象来转换为java对象</span>        User user <span class="token operator">=</span> objectMapper<span class="token punctuation">.</span><span class="token function">readValue</span><span class="token punctuation">(</span>userJson2<span class="token punctuation">,</span> User<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 6.打印结果</span>        System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"user = "</span> <span class="token operator">+</span> user<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>执行完毕回到Redis的图形化客户端查看结果</strong></p><p><img src="https://img.jwt1399.top/img/202212022044794.png"></p><h1 id="六、事务-amp-锁"><a href="#六、事务-amp-锁" class="headerlink" title="六、事务 &amp; 锁"></a>六、事务 &amp; 锁</h1><ul><li><p>Redis 事务是一个单独的隔离操作：事务中的所有命令都会序列化、按顺序地执行。</p></li><li><p>事务在执行的过程中，不会被其他客户端发送来的命令请求所打断。</p></li><li><p>Redis 事务的主要作用就是串联多个命令防止别的命令插队。</p></li></ul><h2 id="1-multi、exec、discard"><a href="#1-multi、exec、discard" class="headerlink" title="1.multi、exec、discard"></a>1.multi、exec、discard</h2><p>从输入 Multi 命令开始，输入的命令都会依次进入命令队列中，但不会执行，直到输入 Exec 后，Redis 会将之前的命令队列中的命令依次执行。组队的过程中可以通过discard来放弃组队。 </p><p><img src="https://img.jwt1399.top/img/202207262126943.png"></p><pre class="line-numbers language-bash"><code class="language-bash">127.0.0.1:6379<span class="token punctuation">[</span>1<span class="token punctuation">]</span><span class="token operator">></span> multiOK127.0.0.1:6379<span class="token punctuation">[</span>1<span class="token punctuation">]</span><span class="token punctuation">(</span>TX<span class="token punctuation">)</span><span class="token operator">></span> <span class="token keyword">set</span> k1 v1QUEUED127.0.0.1:6379<span class="token punctuation">[</span>1<span class="token punctuation">]</span><span class="token punctuation">(</span>TX<span class="token punctuation">)</span><span class="token operator">></span> <span class="token keyword">set</span> k2 v2QUEUED127.0.0.1:6379<span class="token punctuation">[</span>1<span class="token punctuation">]</span><span class="token punctuation">(</span>TX<span class="token punctuation">)</span><span class="token operator">></span> <span class="token function">exec</span>1<span class="token punctuation">)</span> OK2<span class="token punctuation">)</span> OK<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="2-错误处理"><a href="#2-错误处理" class="headerlink" title="2.错误处理"></a>2.错误处理</h2><p>组队中某个命令出现了报告错误，执行时整个的所有队列都会被取消。</p><pre class="line-numbers language-bash"><code class="language-bash">127.0.0.1:6379<span class="token punctuation">[</span>1<span class="token punctuation">]</span><span class="token operator">></span> multiOK127.0.0.1:6379<span class="token punctuation">[</span>1<span class="token punctuation">]</span><span class="token punctuation">(</span>TX<span class="token punctuation">)</span><span class="token operator">></span> <span class="token keyword">set</span> m1 v1QUEUED127.0.0.1:6379<span class="token punctuation">[</span>1<span class="token punctuation">]</span><span class="token punctuation">(</span>TX<span class="token punctuation">)</span><span class="token operator">></span> <span class="token keyword">set</span> m2<span class="token punctuation">(</span>error<span class="token punctuation">)</span> ERR wrong number of arguments <span class="token keyword">for</span> <span class="token string">'set'</span> <span class="token function">command</span>127.0.0.1:6379<span class="token punctuation">[</span>1<span class="token punctuation">]</span><span class="token punctuation">(</span>TX<span class="token punctuation">)</span><span class="token operator">></span> <span class="token keyword">set</span> m3 v3QUEUED127.0.0.1:6379<span class="token punctuation">[</span>1<span class="token punctuation">]</span><span class="token punctuation">(</span>TX<span class="token punctuation">)</span><span class="token operator">></span> <span class="token function">exec</span><span class="token punctuation">(</span>error<span class="token punctuation">)</span> EXECABORT Transaction discarded because of previous errors.<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>如果执行阶段某个命令报出了错误，则只有报错的命令不会被执行，而其他的命令都会执行，不会回滚。</p><pre class="line-numbers language-bash"><code class="language-bash">127.0.0.1:6379<span class="token punctuation">[</span>1<span class="token punctuation">]</span><span class="token operator">></span> multiOK127.0.0.1:6379<span class="token punctuation">[</span>1<span class="token punctuation">]</span><span class="token punctuation">(</span>TX<span class="token punctuation">)</span><span class="token operator">></span> <span class="token keyword">set</span> m1 v1QUEUED127.0.0.1:6379<span class="token punctuation">[</span>1<span class="token punctuation">]</span><span class="token punctuation">(</span>TX<span class="token punctuation">)</span><span class="token operator">></span> incr m1 <span class="token comment" spellcheck="true">#错误语句</span>QUEUED127.0.0.1:6379<span class="token punctuation">[</span>1<span class="token punctuation">]</span><span class="token punctuation">(</span>TX<span class="token punctuation">)</span><span class="token operator">></span> <span class="token keyword">set</span> m2 v2QUEUED127.0.0.1:6379<span class="token punctuation">[</span>1<span class="token punctuation">]</span><span class="token punctuation">(</span>TX<span class="token punctuation">)</span><span class="token operator">></span> <span class="token function">exec</span>1<span class="token punctuation">)</span> OK2<span class="token punctuation">)</span> <span class="token punctuation">(</span>error<span class="token punctuation">)</span> ERR value is not an integer or out of range3<span class="token punctuation">)</span> OK<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="3-事务冲突的问题"><a href="#3-事务冲突的问题" class="headerlink" title="3.事务冲突的问题"></a>3.事务冲突的问题</h2><p>想想一个场景：有很多人有你的账户,同时去参加双十一抢购</p><p>一个请求想给金额减8000</p><p>一个请求想给金额减5000</p><p>一个请求想给金额减1000</p><p><img src="https://img.jwt1399.top/img/202208311123818.png"></p><p>这样就会导致事务冲突，如何解决呢？</p><h3 id="3-1-悲观锁"><a href="#3-1-悲观锁" class="headerlink" title="3.1 悲观锁"></a>3.1 悲观锁</h3><p><strong>悲观锁(Pessimistic Lock)</strong>, 每次去拿数据的时候都认为别人会修改，所以每次在拿数据的时候都会上锁，这样别人想拿这个数据就会 block 直到它拿到锁。</p><p><img src="https://img.jwt1399.top/img/202208311123265.png"></p><p>悲观锁可以实现对于数据的串行化执行，比如syn，和lock都是悲观锁的代表，同时，悲观锁中又可以再细分为公平锁，非公平锁，可重入锁，等等</p><h3 id="3-2-乐观锁"><a href="#3-2-乐观锁" class="headerlink" title="3.2 乐观锁"></a>3.2 乐观锁</h3><p><strong>乐观锁(Optimistic Lock)<strong>，每次去拿数据的时候都认为别人不会修改，所以不会上锁，但是在更新的时候会判断一下在此期间别人有没有去更新这个数据，可以使用版本号等机制。</strong>乐观锁适用于多读的应用类型，这样可以提高吞吐量</strong>。</p><p><img src="https://img.jwt1399.top/img/202208311124559.png"></p><p>乐观锁：会有一个版本号，每次操作数据会对版本号+1，再提交回数据时，会去校验是否比之前的版本大1 ，如果大1 ，则进行操作成功，这套机制的核心逻辑在于，如果在操作过程中，版本号只比原来大1 ，那么就意味着操作过程中没有人对他进行过修改，他的操作就是安全的，如果不大1，则数据被修改过。</p><p>乐观锁的典型代表：就是CAS（<strong>Compare And Swap</strong>），利用cas进行无锁化机制加锁，var5 是操作前读取的内存值，while中的var1+var2 是预估值，如果预估值 &#x3D;&#x3D; 内存值，则代表中间没有被人修改过，此时就将新值去替换内存值，其中do while 是为了在操作失败时，再次进行自旋操作，即把之前的逻辑再操作一次。</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">int</span> var5<span class="token punctuation">;</span><span class="token keyword">do</span> <span class="token punctuation">{</span>    var5 <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">getIntVolatile</span><span class="token punctuation">(</span>var1<span class="token punctuation">,</span> var2<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span> <span class="token keyword">while</span><span class="token punctuation">(</span><span class="token operator">!</span><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">compareAndSwapInt</span><span class="token punctuation">(</span>var1<span class="token punctuation">,</span> var2<span class="token punctuation">,</span> var5<span class="token punctuation">,</span> var5 <span class="token operator">+</span> var4<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">return</span> var5<span class="token punctuation">;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h3 id="3-3-watch-amp-unwatch"><a href="#3-3-watch-amp-unwatch" class="headerlink" title="3.3 watch &amp; unwatch"></a>3.3 watch &amp; unwatch</h3><p>在执行 multi 之前，先执行 watch key1 [key2]，可以监视一个(或多个) key ，如果在事务执行之前这个(或这些) key 被其他命令所改动，那么事务将被打断。</p><pre class="line-numbers language-bash"><code class="language-bash">127.0.0.1:6379<span class="token punctuation">[</span>1<span class="token punctuation">]</span><span class="token operator">></span> <span class="token function">watch</span> balenceOK127.0.0.1:6379<span class="token punctuation">[</span>1<span class="token punctuation">]</span><span class="token operator">></span> multiOK127.0.0.1:6379<span class="token punctuation">[</span>1<span class="token punctuation">]</span><span class="token punctuation">(</span>TX<span class="token punctuation">)</span><span class="token operator">></span> decrby balence 10QUEUED127.0.0.1:6379<span class="token punctuation">[</span>1<span class="token punctuation">]</span><span class="token punctuation">(</span>TX<span class="token punctuation">)</span><span class="token operator">></span> incrby debt 10QUEUED127.0.0.1:6379<span class="token punctuation">[</span>1<span class="token punctuation">]</span><span class="token punctuation">(</span>TX<span class="token punctuation">)</span><span class="token operator">></span> <span class="token function">exec</span>1<span class="token punctuation">)</span> <span class="token punctuation">(</span>integer<span class="token punctuation">)</span> -102<span class="token punctuation">)</span> <span class="token punctuation">(</span>integer<span class="token punctuation">)</span> 10<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p>unwatch 取消 WATCH 命令对所有 key 的监视。如果在执行 WATCH 命令之后，EXEC 命令或DISCARD 命令先被执行了的话，那么就不需要再执行 UNWATCH 了。</p><h2 id="4-Redis事务三特性"><a href="#4-Redis事务三特性" class="headerlink" title="4.Redis事务三特性"></a>4.Redis事务三特性</h2><ul><li><p>单独的隔离操作</p><ul><li>事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中，不会被其他客户端发送来的命令请求所打断。</li></ul></li><li><p>没有隔离级别的概念</p><ul><li>队列中的命令没有提交之前都不会实际被执行，因为事务提交前任何指令都不会被实际执行</li></ul></li><li><p>不保证原子性</p><ul><li>事务中如果有一条命令执行失败，其后的命令仍然会被执行，没有回滚</li></ul></li></ul><h1 id="七、持久化"><a href="#七、持久化" class="headerlink" title="七、持久化"></a>七、持久化</h1><h2 id="1-RDB"><a href="#1-RDB" class="headerlink" title="1.RDB"></a>1.RDB</h2><blockquote><p>RDB(Redis DataBase) 在<strong>指定的时间间隔内</strong>将<strong>内存</strong>中的数据集快照写入<strong>磁盘</strong>， 也就是 Snapshot 快照，它恢复时是将快照文件直接读到内存里</p></blockquote><h3 id="RDB执行步骤"><a href="#RDB执行步骤" class="headerlink" title="RDB执行步骤"></a><strong>RDB执行步骤</strong></h3><p><img src="https://img.jwt1399.top/img/202208312223339.png"></p><ul><li><p>Redis 会单独创建（fork）一个子进程来进行持久化</p><ul><li>Fork 的作用是复制一个与当前进程一样的进程。新进程的所有数据（变量、环境变量、程序计数器等） 数值都和原进程一致，但是是一个全新的进程，并作为原进程的子进程</li><li>在Linux程序中，fork()会产生一个和父进程完全相同的子进程，但子进程在此后多会exec系统调用，出于效率考虑，Linux中引入了“<strong>写时复制技术</strong>”</li><li><strong>一般情况父进程和子进程会共用同一段物理内存</strong>，只有进程空间的各段的内容要发生变化时，才会将父进程的内容复制一份给子进程。</li></ul></li><li><p>先将数据写入到一个临时文件中</p></li><li><p>待持久化过程都结束了，再用这个临时文件替换上次持久化好的文件(dump.rdb)</p><ul><li>在redis.conf中配置持久化文件名称，默认为dump.rdb</li><li>rdb文件的保存路径，也可以修改。默认为Redis启动时命令行所在的目录下</li></ul></li></ul><p>整个过程中，主进程是不进行任何 IO 操作的，这就确保了极高的性能，<strong>RDB的缺点是最后一次持久化后的数据可能丢失</strong>。</p><h3 id="触发RDB策略"><a href="#触发RDB策略" class="headerlink" title="触发RDB策略"></a><strong>触发RDB策略</strong></h3><p><strong>a&gt;配置文件中默认的快照配置</strong></p><p><img src="https://img.jwt1399.top/img/202208312349936.png"></p><p>其中 <code>save 3600 1</code>的含义是：每 3600 秒时，至少有 1 个 key 变化，则触发RDB；save 300 10和save 60 10000同理。</p><p><strong>b&gt;通过 <code>save</code> 或  <code>bgsave</code> 命令触发RDB策略</strong></p><pre class="line-numbers language-sql"><code class="language-sql"><span class="token number">127.0</span><span class="token punctuation">.</span><span class="token number">0.1</span>:<span class="token number">6379</span><span class="token operator">></span><span class="token keyword">save</span><span class="token number">127.0</span><span class="token punctuation">.</span><span class="token number">0.1</span>:<span class="token number">6379</span><span class="token operator">></span>bgsave可以通过lastsave 命令获取最后一次成功执行快照的时间<span class="token number">127.0</span><span class="token punctuation">.</span><span class="token number">0.1</span>:<span class="token number">6379</span><span class="token operator">></span>lastsave<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>save</strong></p><ul><li><p>优点：节约系统资源</p></li><li><p>缺点：直接调用 rdbSave ，阻塞 Redis 主进程，直到保存完成为止。在主进程阻塞期间，服务器不能处理客户端的任何请求。</p></li></ul><p><strong>bgsave</strong></p><ul><li><p>优点：fork 出一个子进程，子进程负责调用 rdbSave ，并在保存完成之后向主进程发送信号，通知保存已完成。 Redis 服务器在BGSAVE 执行期间仍然可以继续处理客户端的请求</p></li><li><p>缺点：由于会fork一个进程，因此更消耗内存</p></li></ul><h3 id="优缺点"><a href="#优缺点" class="headerlink" title="优缺点"></a><strong>优缺点</strong></h3><p>优点</p><ul><li><p>适合大规模的数据恢复</p></li><li><p>对数据完整性和一致性要求不高更适合使用</p></li><li><p>节省磁盘空间</p></li><li><p>恢复速度快</p></li></ul><p>缺点</p><ul><li><p>Fork的时候，内存中的数据被克隆了一份，大致2倍的膨胀性需要考虑</p></li><li><p>虽然Redis在fork时使用了<strong>写时拷贝技术</strong>,但是如果数据庞大时还是比较消耗性能。</p></li><li><p>在备份周期在一定间隔时间做一次备份，所以如果Redis意外down掉的话，就会丢失最后一次快照后的所有修改。</p></li></ul><h2 id="2-AOF"><a href="#2-AOF" class="headerlink" title="2.AOF"></a>2.AOF</h2><blockquote><p>AOF(Append Only File)：以<strong>日志</strong>的形式来记录每个写操作（增量保存），将Redis执行过的所有写指令记录下来(<strong>读操作不记录</strong>)， <strong>只许追加文件但不可以改写文件</strong>，redis启动之初会读取该文件重新构建数据，换言之，redis 重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作</p></blockquote><h3 id="AOF执行步骤"><a href="#AOF执行步骤" class="headerlink" title="AOF执行步骤"></a>AOF执行步骤</h3><p><img src="https://img.jwt1399.top/img/202208312340365.png"></p><ul><li><p>客户端的请求写命令会被append追加到AOF缓冲区内；</p></li><li><p>AOF缓冲区根据AOF持久化策略[always,everysec,no]将操作sync同步到磁盘的AOF文件中；</p><ul><li>appendfsync always 始终同步，每次Redis的写入都会立刻记入日志；性能较差但数据完整性比较好</li><li>appendfsync everysec 每秒同步，每秒记入日志一次，如果宕机，本秒的数据可能丢失。</li><li>appendfsync no  不主动进行同步，把同步时机交给操作系统。</li></ul></li><li><p>AOF文件大小超过重写策略或手动重写时，会对AOF文件rewrite重写，压缩AOF文件容量；</p></li><li><p>Redis服务重启时，会重新load加载AOF文件中的写操作达到数据恢复的目的；</p></li></ul><h3 id="触发AOF策略"><a href="#触发AOF策略" class="headerlink" title="触发AOF策略"></a>触发AOF策略</h3><ul><li><p>AOF默认不开启，需要在配置文件中设置开启</p><ul><li>修改默认的appendonly no，改为yes</li><li>如遇到AOF文件损坏，通过 &#x2F;usr&#x2F;local&#x2F;bin&#x2F;redis-check-aof  –fix appendonly.aof 进行恢复</li></ul></li><li><p>可以在redis.conf中配置文件名称，默认为 appendonly.aof</p></li><li><p>AOF文件的保存路径，同RDB的路径一致。</p></li></ul><h3 id="Rewrite压缩"><a href="#Rewrite压缩" class="headerlink" title="Rewrite压缩"></a>Rewrite压缩</h3><p>AOF采用文件追加方式，文件会越来越大为避免出现此种情况，新增了重写机制, 当AOF文件的大小超过所设定的阈值时，Redis就会启动AOF文件的内容压缩， 只保留可以恢复数据的最小指令集.可以使用命令bgrewriteaof</p><h3 id="优缺点-1"><a href="#优缺点-1" class="headerlink" title="优缺点"></a>优缺点</h3><p>优点</p><ul><li><p>备份机制更稳健，丢失数据概率更低。</p></li><li><p>可读的日志文本，通过操作AOF稳健，可以处理误操作。</p></li></ul><p>缺点</p><ul><li><p>比起RDB占用更多的磁盘空间。</p></li><li><p>恢复备份速度要慢。</p></li><li><p>每次读写都同步的话，有一定的性能压力。</p></li><li><p>存在个别Bug，造成恢复不能。</p></li></ul><h2 id="3-总结"><a href="#3-总结" class="headerlink" title="3.总结"></a>3.总结</h2><p><strong>AOF和RDB同时开启，redis听谁的？</strong></p><p>AOF和RDB同时开启，系统默认取AOF的数据（数据不会存在丢失）</p><p><strong>AOF和RDB用哪个好？</strong></p><p>官方推荐两个都启用。</p><p>如果需要进行大规模数据的恢复，且对于数据恢复的完整性不是非常敏感，那 RDB 方式要比AOF方式更加的高效。不建议单独用 AOF，因为可能会出现Bug。如果只是做纯内存缓存，可以都不用。</p><h1 id="八、主从复制"><a href="#八、主从复制" class="headerlink" title="八、主从复制"></a>八、主从复制</h1><h2 id="1-简介"><a href="#1-简介" class="headerlink" title="1.简介"></a>1.简介</h2><p>主机(master)数据更新后根据配置和策略， 自动同步到备&#x2F;从机(slaver)的机制，Master 以写为主，Slaver 以读为主</p><p><img src="https://img.jwt1399.top/img/202209011443833.png"></p><ul><li><p>可实现读写分离，性能扩展</p></li><li><p>可实现容灾快速恢复</p></li></ul><p><strong>主从复制原理</strong></p><ul><li><p>1、当从连接上主服务器之后，从服务器向主服务器发送数据同步消息；</p></li><li><p>2、主服务器接到从服务器发送过来同步消息，把主服务器数据持久化为 rdb 文件，把 rdb 文件发送给从服务器，从服务器拿到 rdb 进行读取；（全量复制）</p><ul><li>全量复制：而slave服务在接收到数据库文件数据后，将其存盘并加载到内存中。</li></ul></li><li><p>3、之后每次主服务器进行写操作之后，和从服务器进行数据同步（增量复制）（即第一次需要从服务器请求，之后每次主服务器主动同步）</p><ul><li><p>增量复制：Master继续将新的所有收集到的修改命令依次传给slave,完成同步</p></li><li><p>从机挂掉，重新连接主机，自动执行一次全量复制</p></li></ul></li></ul><h2 id="2-模拟一主二仆"><a href="#2-模拟一主二仆" class="headerlink" title="2.模拟一主二仆"></a>2.模拟一主二仆</h2><p><img src="https://img.jwt1399.top/img/202209011629975.png"></p><blockquote><p>模拟三台 redis 服务器（6379、6380、6381），主机（6379）从机（6380、6381）</p><p>我的 redis 配置文件路径： &#x2F;opt&#x2F;homebrew&#x2F;etc&#x2F;redis.conf</p></blockquote><ul><li>1.新建文件夹 myredis</li></ul><pre class="line-numbers language-bash"><code class="language-bash">$ <span class="token function">pwd</span>/Users/jianjian$ <span class="token function">mkdir</span> myredis$ <span class="token function">cd</span>  /myredis$ <span class="token function">pwd</span>/Users/jianjian/myredis<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>2.复制 redis.conf 配置文件到该文件夹</li></ul><pre class="line-numbers language-bash"><code class="language-bash">$ <span class="token function">cp</span> /opt/homebrew/etc/redis.conf  ~/myredis/redis.conf<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><ul><li>3.配置一主两从，创建三个配置文件</li></ul><pre class="line-numbers language-bash"><code class="language-bash">$ <span class="token function">vi</span> redis6379.confinclude /Users/jianjian/myredis/redis.confpidfile /var/run/redis_6379.pidport 6379dbfilename dump6379.rdb$ <span class="token function">vi</span> redis6380.confinclude /Users/jianjian/myredis/redis.confpidfile /var/run/redis_6380.pidport 6380dbfilename dump6380.rdb$ <span class="token function">vi</span> redis6381.confinclude /Users/jianjian/myredis/redis.confpidfile /var/run/redis_6381.pidport 6381dbfilename dump6381.rdb<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>4.启动三台redis服务器</li></ul><pre class="line-numbers language-bash"><code class="language-bash">$ redis-server redis6379.conf$ redis-server redis6380.conf$ redis-server redis6381.conf<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><ul><li>5.查看系统进程，看看三台服务器是否启动</li></ul><pre class="line-numbers language-bash"><code class="language-bash">$ <span class="token function">ps</span> -ef <span class="token operator">|</span> <span class="token function">grep</span> redis501 85018 80912   0  3:54PM ttys000    0:00.60 redis-server 127.0.0.1:6380501 86115 85019   0  3:56PM ttys001    0:00.49 redis-server 127.0.0.1:6379501 86631 86116   0  3:57PM ttys002    0:00.35 redis-server 127.0.0.1:6381<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>6.查看三台主机运行情况</li></ul><pre class="line-numbers language-bash"><code class="language-bash">$ redis-cli -p 6379127.0.0.1:6379<span class="token operator">></span> info replication<span class="token comment" spellcheck="true"># Replication</span>role:masterconnected_slaves:0<span class="token punctuation">..</span><span class="token punctuation">..</span><span class="token punctuation">..</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-bash"><code class="language-bash">$ redis-cli -p 6380127.0.0.1:6380<span class="token operator">></span> info replication<span class="token comment" spellcheck="true"># Replication</span>role:masterconnected_slaves:0<span class="token punctuation">..</span><span class="token punctuation">..</span><span class="token punctuation">..</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-bash"><code class="language-bash">$ redis-cli -p 6381127.0.0.1:6381<span class="token operator">></span> info replication<span class="token comment" spellcheck="true"># Replication</span>role:masterconnected_slaves:0<span class="token punctuation">..</span><span class="token punctuation">..</span><span class="token punctuation">..</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>7.配置从机<ul><li><code>slaveof &lt;ip&gt;&lt;port&gt;</code> 成为某个实例的从服务器</li></ul></li></ul><pre class="line-numbers language-bash"><code class="language-bash"><span class="token comment" spellcheck="true"># 在6380和6381上执行: slaveof 127.0.0.1 6379</span><span class="token comment" spellcheck="true">#变为从机</span>$ redis-cli -p 6380127.0.0.1:6380<span class="token operator">></span>slaveof 127.0.0.1 6379127.0.0.1:6380<span class="token operator">></span> info replication<span class="token comment" spellcheck="true"># Replication</span>role:slavemaster_host:127.0.0.1master_port:6379<span class="token punctuation">..</span><span class="token punctuation">..</span><span class="token punctuation">..</span><span class="token comment" spellcheck="true">#变为从机</span>$ redis-cli -p 6381127.0.0.1:6381<span class="token operator">></span>slaveof 127.0.0.1 6379127.0.0.1:6381<span class="token operator">></span> info replication<span class="token comment" spellcheck="true"># Replication</span>role:slavemaster_host:127.0.0.1master_port:6379<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>8、在主机上写，在从机上可以读取数据</li></ul><pre class="line-numbers language-bash"><code class="language-bash"><span class="token comment" spellcheck="true"># 主机写入数据</span>$ redis-cli -p 6379127.0.0.1:6379<span class="token operator">></span> <span class="token keyword">set</span> k1 v1127.0.0.1:6379<span class="token operator">></span> keys *1<span class="token punctuation">)</span> <span class="token string">"k1"</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-bash"><code class="language-bash"><span class="token comment" spellcheck="true"># 从机可以读取数据</span>$ redis-cli -p 6380127.0.0.1:6380<span class="token operator">></span> keys *1<span class="token punctuation">)</span> <span class="token string">"k1"</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-bash"><code class="language-bash"><span class="token comment" spellcheck="true"># 从机可以读取数据</span>$ redis-cli -p 6381127.0.0.1:6381<span class="token operator">></span> get k11<span class="token punctuation">)</span> <span class="token string">"v1"</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><p><strong>注</strong>：如果主机挂掉，重启就行，一切如初。从机重启需重设：slaveof 127.0.0.1 6379 </p><p>可以将配置增加到文件中。永久生效。</p><p><strong>Q&amp;A</strong></p><p><strong>slave1、slave2是从头开始复制还是从切入点开始复制?比如从k4进来，那之前的k1,k2,k3是否也可以复制？</strong></p><p>从头开始复制；可以</p><p><strong>从机是否可以写？set可否？</strong> </p><p>不可以</p><p><strong>主机shutdown后情况如何？从机是上位还是原地待命？</strong></p><p>主机挂掉，从机原地待命</p><p><strong>主机又回来了后，主机新增记录，从机还能否顺利复制？</strong> </p><p>可以</p><p><strong>其中一台从机down后情况如何？依照原有它能跟上大部队吗？</strong></p><p>从机挂掉重启后需要重设：slaveof 127.0.0.1 6379 </p><h2 id="3-薪火相传"><a href="#3-薪火相传" class="headerlink" title="3.薪火相传"></a>3.薪火相传</h2><p>上一个 Slave 可以是下一个 slave 的Master，slave 同样可以接收其他 slave 的连接和同步请求，那么该 slave 作为了链条中下一个的 master，可以有效减轻 master 的写压力，去中心化降低风险。</p><p><img src="https://img.jwt1399.top/img/202209011637640.png"></p><pre class="line-numbers language-bash"><code class="language-bash">$ redis-cli -p 6380127.0.0.1:6380<span class="token operator">></span>slaveof 127.0.0.1 6379$ redis-cli -p 6381127.0.0.1:6381<span class="token operator">></span>slaveof 127.0.0.1 6380<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><h2 id="4-反客为主"><a href="#4-反客为主" class="headerlink" title="4.反客为主"></a>4.反客为主</h2><blockquote><p>即 主机挂掉之后，备机上位成为主机，保证服务正常进行</p></blockquote><p>当一个 master 宕机后，后面的 slave 可以立刻升为 master，其后面的 slave 不用做任何修改。用 <code>slaveof no one</code>  将从机变为主机。</p><pre class="line-numbers language-bash"><code class="language-bash"><span class="token comment" spellcheck="true"># master 宕机</span>$ redis-cli -p 6379127.0.0.1:6379<span class="token operator">></span> <span class="token function">shutdown</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-bash"><code class="language-bash"><span class="token comment" spellcheck="true"># slave1升为master</span>$ redis-cli -p 6380127.0.0.1:6380<span class="token operator">></span> slave no one127.0.0.1:6380<span class="token operator">></span> <span class="token keyword">set</span> k2 v2<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-bash"><code class="language-bash">$ redis-cli -p 6381127.0.0.1:6381<span class="token operator">></span> get k2<span class="token string">"v2"</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><h2 id="5-哨兵模式"><a href="#5-哨兵模式" class="headerlink" title="5.哨兵模式"></a>5.哨兵模式</h2><p><strong>反客为主的自动版</strong>，能够后台监控主机是否故障，如果故障了根据投票数自动将从机转换为主机</p><p><img src="https://img.jwt1399.top/img/202209011737377.png"></p><p><strong>配置哨兵</strong></p><ul><li>新建 sentinel.conf 文件，写入如下内容</li></ul><pre class="line-numbers language-bash"><code class="language-bash">$ <span class="token function">vi</span> sentinel.confsentinel monitor mymaster 127.0.0.1 6379 1<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre><p>其中 mymaster 为监控对象起的服务器名称， 1 为至少有多少个哨兵同意迁移的数量。 </p><p><strong>启动哨兵</strong></p><pre class="line-numbers language-bash"><code class="language-bash">$ redis-sentinel sentinel.conf <span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p><img src="https://img.jwt1399.top/img/202209011811442.png"></p><p><strong>当主机挂掉，从机选举中产生新的主机</strong></p><pre class="line-numbers language-bash"><code class="language-bash">$ 127.0.0.1:6379<span class="token operator">></span> <span class="token function">shutdown</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p><img src="https://img.jwt1399.top/img/202209011812295.png"></p><p>这里显示6380变成了主机，原主机6379重启后会变为从机。</p><p><strong>哪个从机会被选举为主机呢？根据优先级别：replica-priority</strong></p><p><img src="https://img.jwt1399.top/img/202209011806743.png"></p><ul><li><p>优先级在redis.conf中默认：replica-priority 100 值，越小优先级越高</p></li><li><p>偏移量是指获得原主机数据最全的</p></li><li><p>每个redis实例启动后都会随机生成一个40位的runid</p></li></ul><h1 id="九、集群"><a href="#九、集群" class="headerlink" title="九、集群"></a>九、集群</h1><p>Redis 集群实现了对Redis的水平扩容，即启动N个redis节点，将整个数据库分布存储在这N个节点中，每个节点存储总数据的1&#x2F;N。Redis 集群通过分区（partition）来提供一定程度的可用性（availability）： 即使集群中有一部分节点失效或者无法进行通讯， 集群也可以继续处理命令请求。</p><p><strong>Redis</strong> <strong>集群提供了以下好处</strong></p><ul><li><p>实现扩容</p></li><li><p>分摊压力</p></li><li><p>无中心配置相对简单</p></li></ul><p><strong>Redis</strong> <strong>集群的不足</strong></p><ul><li><p>多键操作是不被支持的 </p></li><li><p>多键的Redis事务是不被支持的。lua脚本不被支持</p></li><li><p>由于集群方案出现较晚，很多公司已经采用了其他的集群方案，而代理或者客户端分片的方案想要迁移至redis cluster，需要整体迁移而不是逐步过渡，复杂度较大。</p></li></ul><h2 id="1-模拟集群"><a href="#1-模拟集群" class="headerlink" title="1.模拟集群"></a>1.模拟集群</h2><blockquote><p>模拟6台 redis 服务器，6379、6380、6381、6389、6390、6391</p><p>主机（6379、6380、6381）；从机（6389、6390、6391），将他们加入一个集群中</p></blockquote><ul><li>配置 redis6379.conf 文件，拷贝 5 个 redis6379.conf 文件</li></ul><pre class="line-numbers language-bash"><code class="language-bash">$ <span class="token function">vi</span> redis6379.confinclude /Users/jianjian/myredis/redis.confpidfile /var/run/redis_6379.pidport 6379dbfilename dump6379.rdb<span class="token comment" spellcheck="true">## cluster配置修改</span><span class="token function">dir</span> <span class="token string">"/Users/jianjian/myredis/redis_cluster"</span>logfile <span class="token string">"/Users/jianjian/myredis/redis_cluster/redis_err_6379.log"</span><span class="token comment" spellcheck="true"># 打开集群模式</span>cluster-enabled <span class="token function">yes</span><span class="token comment" spellcheck="true"># 设定节点配置文件名</span>cluster-config-file nodes-6379.conf<span class="token comment" spellcheck="true"># 设定节点失联时间，超过该时间（毫秒），集群自动进行主从切换</span>cluster-node-timeout 15000<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><pre class="line-numbers language-bash"><code class="language-bash">$ <span class="token function">cp</span> redis6379.conf redis6380.conf$ <span class="token function">cp</span> redis6379.conf redis6381.conf$ <span class="token function">cp</span> redis6379.conf redis6389.conf$ <span class="token function">cp</span> redis6379.conf redis6390.conf$ <span class="token function">cp</span> redis6379.conf redis6391.conf<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre><ul><li>使用查找替换修改拷贝的5个文件</li></ul><pre class="line-numbers language-bash"><code class="language-bash"><span class="token comment" spellcheck="true">#将redis6380.conf中的6379替换为6380</span>$ <span class="token function">vi</span> redis6380.conf:%s/6379/6380  <span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><ul><li>启动 6个 redis 服务</li></ul><p><img src="https://img.jwt1399.top/img/202210242303751.png"></p><ul><li>将六个节点合成一个集群</li></ul><p>合成之前，请确保所有redis实例启动后，nodes-xxxx.conf文件都生成正常。</p><pre class="line-numbers language-bash"><code class="language-bash">$ redis-cli --cluster create --cluster-replicas 1 192.168.11.101:6379 192.168.11.101:6380 192.168.11.101:6381 192.168.11.101:6389 192.168.11.101:6390 192.168.11.101:6391<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p>此处不要用127.0.0.1， 请用真实IP地址；<code>--cluster-replicas 1</code> 表示采用最简单的方式配置集群，一台主机，一台从机，正好三组。</p><ul><li>采用集群策略连接，设置数据会自动切换到相应的写主机</li></ul><pre class="line-numbers language-bash"><code class="language-bash">$ redis-cli -c -p 6379<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><ul><li>查看集群信息</li></ul><pre class="line-numbers language-bash"><code class="language-bash">127.0.0.1:6379<span class="token operator">></span> cluster nodes<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><h2 id="2-slots"><a href="#2-slots" class="headerlink" title="2.slots"></a>2.slots</h2><p><strong>什么是slots</strong></p><p>一个 Redis 集群包含 16384 个插槽（hash slot）， 数据库中的每个键都属于这 16384 个插槽的其中一个， 集群使用公式 CRC16(key) % 16384 来计算键 key 属于哪个槽， 其中 CRC16(key) 语句用于计算键 key 的 CRC16 校验和 。</p><p>集群中的每个节点负责处理一部分插槽。 举个例子， 如果一个集群可以有主节点， 其中：</p><p>节点 A 负责处理 0 号至 5460 号插槽。</p><p>节点 B 负责处理 5461 号至 10922 号插槽。</p><p>节点 C 负责处理 10923 号至 16383 号插槽。</p><p><strong>在集群中录入值</strong></p><p>在redis-cli每次录入、查询键值，redis都会计算出该key应该送往的插槽 slot ，如果不是该客</p><p>户端对应服务器的插槽，redis会报错，并告知应前往的redis实例地址和端口。</p><p>redis-cli客户端提供了 –c 参数实现自动重定向。如 redis-cli -c –p 6379 登入后，再录入、查询键值对可以自动重定向。</p><ul><li>不在一个slot下的键值，是不能使用mget,mset等多键操作。</li></ul><p><img src="https://img.jwt1399.top/img/202209022233587.png"></p><ul><li>可以通过{}来定义组的概念，从而使key中{}内相同内容的键值对放到一个slot中去。</li></ul><p><img src="https://img.jwt1399.top/img/202209022233682.png"></p><ul><li>查询集群中的值</li></ul><p><code>CLUSTER GETKEYSINSLOT &lt;slot&gt;&lt;count&gt;</code> 返回 count 个 slot 槽中的键</p><p><img src="https://img.jwt1399.top/img/202209022234749.png"></p><h2 id="3-故障恢复"><a href="#3-故障恢复" class="headerlink" title="3.故障恢复"></a>3.故障恢复</h2><p>如果主节点下线？从节点能否自动升为主节点？注意：15秒超时</p><p>…                               </p><p>主节点恢复后，主从关系会如何？</p><p>主节点回来变成从机。</p><p>如果所有某一段插槽的主从节点都宕掉，redis服务是否还能继续?</p><ul><li><p>如果某一段插槽的主从都挂掉，而cluster-require-full-coverage 为yes ，那么 ，整个集群都挂掉</p></li><li><p>如果某一段插槽的主从都挂掉，而cluster-require-full-coverage 为no ，那么，该插槽数据全都不能使用，也无法存储。</p></li></ul><h2 id="4-集群的-Jedis-开发"><a href="#4-集群的-Jedis-开发" class="headerlink" title="4.集群的 Jedis 开发"></a>4.集群的 Jedis 开发</h2><p>即使连接的不是主机，集群会自动切换主机存储。主机写，从机读。</p><p>无中心化主从集群。无论从哪台主机写的数据，其他主机上都能读到数据。</p><pre class="line-numbers language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">JedisClusterTest</span> <span class="token punctuation">{</span>  <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span>      Set<span class="token operator">&lt;</span>HostAndPort<span class="token operator">></span>set <span class="token operator">=</span><span class="token keyword">new</span> <span class="token class-name">HashSet</span><span class="token operator">&lt;</span>HostAndPort<span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>     set<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">HostAndPort</span><span class="token punctuation">(</span><span class="token string">"192.168.31.211"</span><span class="token punctuation">,</span><span class="token number">6379</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>     JedisCluster jedisCluster<span class="token operator">=</span><span class="token keyword">new</span> <span class="token class-name">JedisCluster</span><span class="token punctuation">(</span>set<span class="token punctuation">)</span><span class="token punctuation">;</span>     jedisCluster<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span><span class="token string">"k1"</span><span class="token punctuation">,</span> <span class="token string">"v1"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>     System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>jedisCluster<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">"k1"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>  <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><h1 id="十、应用问题"><a href="#十、应用问题" class="headerlink" title="十、应用问题"></a>十、应用问题</h1><h2 id="1-缓存穿透"><a href="#1-缓存穿透" class="headerlink" title="1.缓存穿透"></a>1.缓存穿透</h2><blockquote><p>问题描述</p></blockquote><p>key 对应的数据在数据源并不存在，每次针对此 key 的请求从缓存获取不到，请求都会压到数据源，从而可能压垮数据源。比如用一个不存在的用户 id 获取用户信息，不论缓存还是数据库都没有，若黑客利用此漏洞进行攻击可能压垮数据库。</p><ul><li>1、应用服务器压力变大了</li><li>2、redis 命中率降低</li><li>3、 一直查询数据库</li></ul><p><img src="https://img.jwt1399.top/img/202209031811627.png"></p><p>一个一定不存在缓存及查询不到的数据，由于缓存是不命中时被动写的，并且出于容错考虑，如果从存储层查不到数据则不写入缓存，这将导致这个不存在的数据每次请求都要到存储层去查询，失去了缓存的意义。</p><blockquote><p>解决方案</p></blockquote><ul><li><p>1、<strong>对空值缓存：</strong>如果一个查询返回的数据为空（不管是数据是否不存在），我们仍然把这个空结果（null）进行缓存，设置空结果的过期时间会很短，最长不超过五分钟</p></li><li><p>2、<strong>设置可访问的名单（白名单）：</strong>使用 bitmaps 类型定义一个可以访问的名单，名单 id作为 bitmaps 的偏移量，每次访问和 bitmap 里面的 id 进行比较，如果访问 id 不在bitmaps 里面，进行拦截，不允许访问。</p></li><li><p>3、<strong>采用布隆过滤器</strong>：布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法，缺点是有一定的误识别率和删除困难。将所有可能存在的数据哈希到一个足够大的布隆过滤器中，一个一定不存在的数据会被这个布隆过滤器拦截掉，从而避免了对底层存储系统的查询压力。</p></li><li><p>4、<strong>进行实时监控：</strong>当发现Redis的命中率开始急速降低，需要排查访问对象和访问的数据，和运维人员配合，可以设置黑名单限制服务</p></li></ul><h2 id="2-缓存击穿"><a href="#2-缓存击穿" class="headerlink" title="2.缓存击穿"></a>2.缓存击穿</h2><blockquote><p>问题描述</p></blockquote><p>key 对应的数据存在，但在 redis 中过期，此时若有大量并发请求过来，这些请求发现缓存过期一般都会从后端 DB 加载数据并回设到缓存，这个时候大并发的请求可能会瞬间把后端 DB 压垮。</p><ul><li>1、数据库访问压力瞬时增加</li><li>2、redis 某个 key 过期了，大量访问使用这个key</li><li>3、redis 正常运行</li></ul><p><img src="https://img.jwt1399.top/img/202209031817897.png">                               </p><p>key可能会在某些时间点被超高并发地访问，是一种非常“热点”的数据。这个时候，需要考虑一个问题：缓存被“击穿”的问题。</p><blockquote><p> 解决方案</p></blockquote><ul><li><p>1、<strong>预先设置热门数据</strong>：在redis高峰访问之前，把一些热门数据提前存入到redis里面，加大这些热门数据key的时长</p></li><li><p>2、<strong>实时调整</strong>：现场监控哪些数据热门，实时调整key的过期时长</p></li><li><p>3、<strong>使用锁</strong>：</p><ul><li>1.就是在缓存失效的时候（判断拿出来的值为空），不是立即去load db。</li><li>2.先使用缓存工具的某些带成功操作返回值的操作（比如Redis的SETNX）去set一个mutex key</li><li>3.当操作返回成功时，再进行load db的操作，并回设缓存，最后删除mutex key；</li><li>4.当操作返回失败，证明有线程在load db，当前线程睡眠一段时间再重试整个get缓存的方法。</li></ul><p><img src="https://img.jwt1399.top/img/202209031826143.png"></p></li></ul><h2 id="3-缓存雪崩"><a href="#3-缓存雪崩" class="headerlink" title="3.缓存雪崩"></a>3.缓存雪崩</h2><blockquote><p>问题描述</p></blockquote><p>key 对应的数据存在，但在 redis 中过期，此时若有大量并发请求过来，这些请求发现缓存过期一般都会从后端 DB 加载数据并回设到缓存，这个时候大并发的请求可能会瞬间把后端DB压垮。缓存雪崩与缓存击穿的区别在于这里针对很多 key 缓存，前者则是某一个 key。</p><ul><li>1、数据库压力变大服务器崩溃</li><li>2、在极少时间段，查询大量 key 的集中过期</li></ul><p><img src="https://img.jwt1399.top/img/202209031831902.png"></p><p>缓存失效时的雪崩效应对底层系统的冲击非常可怕！</p><blockquote><p>解决方案</p></blockquote><ul><li><p>1、 <strong>构建多级缓存架构：</strong>nginx缓存 + redis缓存 +其他缓存（ehcache等）</p></li><li><p>2、<strong>使用锁或队列：</strong>用加锁或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写，从而避免失效时大量的并发请求落到底层存储系统上。不适用高并发情况</p></li><li><p>3、  <strong>设置过期标志更新缓存：</strong>记录缓存数据是否过期（设置提前量），如果过期会触发通知另外的线程在后台去更新实际key的缓存。</p></li><li><p>4、<strong>将缓存失效时间分散开：</strong>比如我们可以在原有的失效时间基础上增加一个随机值，比如1-5分钟随机，这样每一个缓存的过期时间的重复率就会降低，就很难引发集体失效的事件。</p></li></ul><h2 id="4-分布式锁"><a href="#4-分布式锁" class="headerlink" title="4.分布式锁"></a>4.分布式锁</h2><blockquote><p>问题描述</p></blockquote><p>随着业务发展的需要，原单体单机部署的系统被演化成分布式集群系统后，由于分布式系统多线程、多进程并且分布在不同机器上，这将使原单机部署情况下的并发控制锁策略失效，单纯的Java API并不能提供分布式锁的能力。为了解决这个问题就需要一种跨JVM的互斥机制来控制共享资源的访问，这就是分布式锁要解决的问题！</p><blockquote><p>解决方案</p></blockquote><p>分布式锁主流的实现方案：</p><ul><li><p>1.基于数据库实现分布式锁</p></li><li><p>2.基于缓存（Redis等）</p></li><li><p>3.基于Zookeeper</p></li></ul><p>每一种分布式锁解决方案都有各自的优缺点：</p><ul><li><p>性能：redis最高</p></li><li><p>可靠性：zookeeper最高</p></li></ul><p>这里，我们就基于redis实现分布式锁。</p><pre class="line-numbers language-bash"><code class="language-bash">$ 127.0.0.1:6379<span class="token operator">></span>  <span class="token keyword">set</span> key value EX second$ 127.0.0.1:6379<span class="token operator">></span>  <span class="token keyword">set</span> sku:1:info “OK” NX PX 10000<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre><ul><li>EX second ：设置键的过期时间为 second 秒。 SET key value EX second 效果等同于 SETEX key second value 。</li><li>PX millisecond ：设置键的过期时间为 millisecond 毫秒。 SET key value PX millisecond 效果等同于 PSETEX key millisecond value 。</li><li>NX ：只在键不存在时，才对键进行设置操作。 SET key value NX 效果等同于 SETNX key value 。</li><li>XX ：只在键已经存在时，才对键进行设置操作。</li></ul><p><img src="https://img.jwt1399.top/img/202209042203987.png"></p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@GetMapping</span><span class="token punctuation">(</span><span class="token string">"testLock"</span><span class="token punctuation">)</span><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">testLock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">//1获取锁，setne</span>    Boolean lock <span class="token operator">=</span> redisTemplate<span class="token punctuation">.</span><span class="token function">opsForValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">setIfAbsent</span><span class="token punctuation">(</span><span class="token string">"lock"</span><span class="token punctuation">,</span> <span class="token string">"111"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">//2获取锁成功、查询num的值</span>    <span class="token keyword">if</span><span class="token punctuation">(</span>lock<span class="token punctuation">)</span><span class="token punctuation">{</span>        Object value <span class="token operator">=</span> redisTemplate<span class="token punctuation">.</span><span class="token function">opsForValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">"num"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">//2.1判断num为空return</span>        <span class="token keyword">if</span><span class="token punctuation">(</span>StringUtils<span class="token punctuation">.</span><span class="token function">isEmpty</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">{</span>            <span class="token keyword">return</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">//2.2有值就转成成int</span>        <span class="token keyword">int</span> num <span class="token operator">=</span> Integer<span class="token punctuation">.</span><span class="token function">parseInt</span><span class="token punctuation">(</span>value<span class="token operator">+</span><span class="token string">""</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">//2.3把redis的num加1</span>        redisTemplate<span class="token punctuation">.</span><span class="token function">opsForValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span><span class="token string">"num"</span><span class="token punctuation">,</span> <span class="token operator">++</span>num<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">//2.4释放锁，del</span>        redisTemplate<span class="token punctuation">.</span><span class="token function">delete</span><span class="token punctuation">(</span><span class="token string">"lock"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span><span class="token keyword">else</span><span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">//3获取锁失败、每隔0.1秒再获取</span>        <span class="token keyword">try</span> <span class="token punctuation">{</span>            Thread<span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">100</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token function">testLock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">InterruptedException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span>            e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><blockquote><p><strong>优化之设置锁的过期时间</strong></p></blockquote><p>问题：setnx刚好获取到锁，业务逻辑出现异常，导致锁无法释放</p><p>解决：设置过期时间，自动释放锁。</p><p>设置过期时间有两种方式：</p><ul><li>1.首先想到通过expire设置过期时间（缺乏原子性：如果在setnx和expire之间出现异常，锁也无法释放）</li><li>2.在set时指定过期时间（推荐）</li></ul><p><img src="https://img.jwt1399.top/img/202209042204776.png"></p><p><img src="https://img.jwt1399.top/img/202209042205533.png"></p><blockquote><p><strong>优化之UUID防误删</strong></p></blockquote><p>场景：如果业务逻辑的执行时间是7s。执行流程如下</p><ol><li><p>index1业务逻辑没执行完，3秒后锁被自动释放。</p></li><li><p>index2获取到锁，执行业务逻辑，3秒后锁被自动释放。</p></li><li><p>index3获取到锁，执行业务逻辑</p></li><li><p>index1业务逻辑执行完成，开始调用del释放锁，这时释放的是index3的锁，导致index3的业务只执行1s就被别人释放。最终等于没锁的情况。</p></li></ol><p>解决：setnx获取锁时，设置一个指定的唯一值（例如：uuid）；释放前获取这个值，判断是否自己的锁</p><p><img src="https://img.jwt1399.top/img/202209042210208.png"></p><p><img src="https://img.jwt1399.top/img/202209042210135.png"></p><blockquote><p><strong>优化之LUA脚本保证删除的原子性</strong></p></blockquote><p>问题：删除操作缺乏原子性。</p><p>场景：</p><ol><li>index1执行删除时，查询到的lock值确实和uuid相等</li></ol><pre><code>uuid=v1set(lock,uuid)；</code></pre><p><img src="https://img.jwt1399.top/img/202209042212222.png">                         </p><ol start="2"><li>index1执行删除前，lock刚好过期时间已到，被redis自动释放</li></ol><p>在redis中没有了lock，没有了锁。</p><p><img src="https://img.jwt1399.top/img/202209042212030.png"> </p><ol start="3"><li>index2获取了lock</li></ol><p>index2线程获取到了cpu的资源，开始执行方法</p><pre><code>uuid=v2set(lock,uuid)；</code></pre><ol start="4"><li>index1执行删除，此时会把index2的lock删除</li></ol><p>index1 因为已经在方法中了，所以不需要重新上锁。index1有执行的权限。index1已经比较完成了，这个时候，开始执行</p><p><img src="https://img.jwt1399.top/img/202209042212365.png"></p><p>删除的index2的锁！</p><pre class="line-numbers language-java"><code class="language-java"><span class="token annotation punctuation">@GetMapping</span><span class="token punctuation">(</span><span class="token string">"testLockLua"</span><span class="token punctuation">)</span><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">testLockLua</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>    <span class="token comment" spellcheck="true">//1 声明一个uuid ,将做为一个value 放入我们的key所对应的值中</span>    String uuid <span class="token operator">=</span> UUID<span class="token punctuation">.</span><span class="token function">randomUUID</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">//2 定义一个锁：lua 脚本可以使用同一把锁，来实现删除！</span>    String skuId <span class="token operator">=</span> <span class="token string">"25"</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 访问skuId 为25号的商品 100008348542</span>    String locKey <span class="token operator">=</span> <span class="token string">"lock:"</span> <span class="token operator">+</span> skuId<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 锁住的是每个商品的数据</span>    <span class="token comment" spellcheck="true">// 3 获取锁</span>    Boolean lock <span class="token operator">=</span> redisTemplate<span class="token punctuation">.</span><span class="token function">opsForValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">setIfAbsent</span><span class="token punctuation">(</span>locKey<span class="token punctuation">,</span> uuid<span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">,</span> TimeUnit<span class="token punctuation">.</span>SECONDS<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token comment" spellcheck="true">// 第一种： lock 与过期时间中间不写任何的代码。</span>    <span class="token comment" spellcheck="true">// redisTemplate.expire("lock",10, TimeUnit.SECONDS);//设置过期时间</span>    <span class="token comment" spellcheck="true">// 如果true</span>    <span class="token keyword">if</span> <span class="token punctuation">(</span>lock<span class="token punctuation">)</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 执行的业务逻辑开始</span>        <span class="token comment" spellcheck="true">// 获取缓存中的num 数据</span>        Object value <span class="token operator">=</span> redisTemplate<span class="token punctuation">.</span><span class="token function">opsForValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">"num"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 如果是空直接返回</span>        <span class="token keyword">if</span> <span class="token punctuation">(</span>StringUtils<span class="token punctuation">.</span><span class="token function">isEmpty</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>            <span class="token keyword">return</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>        <span class="token comment" spellcheck="true">// 不是空 如果说在这出现了异常！ 那么delete 就删除失败！ 也就是说锁永远存在！</span>        <span class="token keyword">int</span> num <span class="token operator">=</span> Integer<span class="token punctuation">.</span><span class="token function">parseInt</span><span class="token punctuation">(</span>value <span class="token operator">+</span> <span class="token string">""</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 使num 每次+1 放入缓存</span>        redisTemplate<span class="token punctuation">.</span><span class="token function">opsForValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span><span class="token string">"num"</span><span class="token punctuation">,</span> String<span class="token punctuation">.</span><span class="token function">valueOf</span><span class="token punctuation">(</span><span class="token operator">++</span>num<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">/*使用lua脚本来锁*/</span>        <span class="token comment" spellcheck="true">// 定义lua 脚本</span>        String script <span class="token operator">=</span> <span class="token string">"if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 使用redis执行lua执行</span>        DefaultRedisScript<span class="token operator">&lt;</span>Long<span class="token operator">></span> redisScript <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">DefaultRedisScript</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        redisScript<span class="token punctuation">.</span><span class="token function">setScriptText</span><span class="token punctuation">(</span>script<span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 设置一下返回值类型 为Long</span>        <span class="token comment" spellcheck="true">// 因为删除判断的时候，返回的0,给其封装为数据类型。如果不封装那么默认返回String 类型，</span>        <span class="token comment" spellcheck="true">// 那么返回字符串与0 会有发生错误。</span>        redisScript<span class="token punctuation">.</span><span class="token function">setResultType</span><span class="token punctuation">(</span>Long<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token comment" spellcheck="true">// 第一个要是script 脚本 ，第二个需要判断的key，第三个就是key所对应的值。</span>        redisTemplate<span class="token punctuation">.</span><span class="token function">execute</span><span class="token punctuation">(</span>redisScript<span class="token punctuation">,</span> Arrays<span class="token punctuation">.</span><span class="token function">asList</span><span class="token punctuation">(</span>locKey<span class="token punctuation">)</span><span class="token punctuation">,</span> uuid<span class="token punctuation">)</span><span class="token punctuation">;</span>    <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>        <span class="token comment" spellcheck="true">// 其他线程等待</span>        <span class="token keyword">try</span> <span class="token punctuation">{</span>            <span class="token comment" spellcheck="true">// 睡眠</span>            Thread<span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">1000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>            <span class="token comment" spellcheck="true">// 睡醒了之后，调用方法。</span>            <span class="token function">testLockLua</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">InterruptedException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span>            e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>        <span class="token punctuation">}</span>    <span class="token punctuation">}</span><span class="token punctuation">}</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre><p><strong>总结</strong></p><p>为了确保分布式锁可用，我们至少要确保锁的实现同时满足以下四个条件：</p><ul><li><strong>互斥性。</strong>在任意时刻，只有一个客户端能持有锁。</li><li><strong>不会发生死锁。</strong>即使有一个客户端在持有锁的期间崩溃而没有主动解锁，也能保证后续其他客户端能加锁。</li><li><strong>解铃还须系铃人。</strong>加锁和解锁必须是同一个客户端，客户端自己不能把别人加的锁给解了。</li><li><strong>加锁和解锁必须具有原子性。</strong></li></ul><p>同一时间只有一个人有锁，而且开锁解锁都是同一个人，不会死锁</p><h1 id="十一、6-0新功能"><a href="#十一、6-0新功能" class="headerlink" title="十一、6.0新功能"></a>十一、6.0新功能</h1><h2 id="1-ACL"><a href="#1-ACL" class="headerlink" title="1.ACL"></a>1.ACL</h2><h3 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h3><p>Redis ACL是Access Control List（访问控制列表）的缩写，该功能允许根据可以执行的命令和可以访问的键来限制某些连接。</p><p>在Redis 5版本之前，Redis 安全规则只有密码控制 还有通过 rename 来调整高危命令比如 flushdb ， KEYS* ， shutdown 等。Redis 6 则提供ACL的功能对用户进行更细粒度的权限控制 ：</p><p>（1）接入权限：用户名和密码 </p><p>（2）可以执行的命令 </p><p>（3）可以操作的 KEY</p><h3 id="命令"><a href="#命令" class="headerlink" title="命令"></a>命令</h3><ul><li><code>acl list</code> 展现用户权限列表</li></ul><p><img src="https://img.jwt1399.top/img/202209042132049.png"></p><ul><li><code>acl cat</code> 查看添加权限指令类别，加参数类型名可以查看类型下具体命令</li></ul><table><thead><tr><th><img src="https://img.jwt1399.top/img/202209042135546.png"></th><th><img src="https://img.jwt1399.top/img/202209042135820.png"></th></tr></thead></table><ul><li><code>acl whoami</code> 查看当前用户</li></ul><p><img src="https://img.jwt1399.top/img/202209042136959.png"></p><ul><li><code>acl setuser</code> 创建和编辑用户ACL</li></ul><p>下面是有效ACL规则的列表。某些规则只是用于激活或删除标志，或对用户ACL执行给定更改的单个单词。其他规则是字符前缀，它们与命令或类别名称、键模式等连接在一起。</p><p><img src="https://img.jwt1399.top/img/202209042146682.png"></p><ul><li>创建新用户默认权限</li></ul><pre class="line-numbers language-bash"><code class="language-bash">acl setuser 用户名<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p><img src="https://img.jwt1399.top/img/202209042149085.png"></p><ul><li>设置有用户名、密码、ACL权限、并启用的用户</li></ul><pre class="line-numbers language-bash"><code class="language-bash">acl setuser user2 on <span class="token operator">></span>password ~cached:* +get<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p><img src="https://img.jwt1399.top/img/202209042150739.png"></p><ul><li>切换用户，验证权限</li></ul><pre class="line-numbers language-bash"><code class="language-bash">auth user2 password<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><p><img src="https://img.jwt1399.top/img/202209042150436.png"></p><h2 id="2-IO多线程"><a href="#2-IO多线程" class="headerlink" title="2.IO多线程"></a>2.IO多线程</h2><p>Redis 6终于支持多线程了，告别单线程了吗？</p><p>IO 多线程其实指<strong>客户端交互部分</strong>的<strong>网络IO</strong>交互处理模块<strong>多线程</strong>，而非<strong>执行命令多线程</strong>。Redis6执行命令依然是单线程。</p><p> <strong>原理架构</strong></p><p>Redis 6 加入多线程，但跟 Memcached 这种从 IO处理到数据访问多线程的实现模式有些差异。Redis 的多线程部分只是用来处理网络数据的读写和协议解析，执行命令仍然是单线程。之所以这么设计是不想因为多线程而变得复杂，需要去控制 key、lua、事务，LPUSH&#x2F;LPOP 等等的并发问题。整体的设计大体如下:</p><p><img src="https://img.jwt1399.top/img/202209042159608.png"></p><p>另外，多线程IO默认也是不开启的，需要在配置文件中配置</p><pre><code>io-threads-do-reads yes io-threads 4</code></pre><h2 id="3-Cluster"><a href="#3-Cluster" class="headerlink" title="3.Cluster"></a>3.Cluster</h2><p>之前老版 Redis 想要搭集群需要单独安装 ruby 环境，Redis 5 将 redis-trib.rb 的功能集成到 redis-cli 。另外官方 redis-benchmark 工具开始支持 cluster 模式了，通过多线程的方式对多个分片进行压测。</p><h2 id="4-其它功能"><a href="#4-其它功能" class="headerlink" title="4.其它功能"></a>4.其它功能</h2><p>1、RESP3新的 Redis 通信协议：优化服务端与客户端之间通信</p><p>2、Client side caching客户端缓存：基于 RESP3 协议实现的客户端缓存功能。为了进一步提升缓存的性能，将客户端经常访问的数据cache到客户端。减少TCP网络交互。</p><p>3、Proxy集群代理模式：Proxy 功能，让 Cluster 拥有像单实例一样的接入方式，降低大家使用cluster的门槛。不过需要注意的是代理不改变 Cluster 的功能限制，不支持的命令还是不会支持，比如跨 slot 的多Key操作。</p><p>4、Modules API：Redis 6中模块API开发进展非常大，因为Redis Labs为了开发复杂的功能，从一开始就用上Redis模块。Redis可以变成一个框架，利用Modules来构建不同系统，而不需要从头开始写然后还要BSD许可。Redis一开始就是一个向编写各种系统开放的平台。</p><h1 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h1><ul><li><a href="https://blog.csdn.net/weixin_47872288/article/details/118410080"> Redis框架从入门到学精（全）</a></li><li><a href="https://www.oz6.cn/articles/58">Redis基础篇 (oz6.cn)</a></li></ul><h1 id="Sponsor❤️"><a href="#Sponsor❤️" class="headerlink" title="Sponsor❤️"></a>Sponsor❤️</h1><p>您的支持是我不断前进的动力，如果您感觉本文对您有所帮助的话，可以考虑打赏一下本文，用以维持本博客的运营费用，拒绝白嫖，从你我做起！🥰🥰🥰</p><table>  <tbody>     <tr>         <td style="text-align:center;">支付宝</td>         <td style="text-align:center;">微信</td>     </tr>   <tr>    <td style="text-align:center;" ><img width="200" src="https://jwt1399.top/medias/reward/alipay.png"></td>          <td style="text-align:center;"><img width="200" src="https://jwt1399.top/medias/reward/sponor_wechat.png"></td>       </tr></tbody></table>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;h1 id=&quot;一、Redis简介&quot;&gt;&lt;a href=&quot;#一、Redis简介&quot; class=&quot;headerlink&quot; title=&quot;一、Redis简介&quot;&gt;&lt;/a&gt;一、Redis简介&lt;/h1&gt;&lt;h2 id=&quot;1-认识NoSQL&quot;&gt;&lt;a href=&quot;#1-认识NoSQL&quot;</summary>
        
      
    
    
    
    <category term="SQL" scheme="https://jwt1399.top/categories/SQL/"/>
    
    
    <category term="NoSQL" scheme="https://jwt1399.top/tags/NoSQL/"/>
    
  </entry>
  
  <entry>
    <title>SSM整合</title>
    <link href="https://jwt1399.top/posts/42096.html"/>
    <id>https://jwt1399.top/posts/42096.html</id>
    <published>2022-05-26T13:01:21.000Z</published>
    <updated>2022-09-12T05:16:05.213Z</updated>
    
    <content type="html"><![CDATA[<p>今天开始学习我自己总结的 <a href="https://jwt1399.top/posts/29829.html">Java-学习路线</a> 中的《SSM整合》，小简从 0 开始学 Java 知识，并不定期更新所学笔记，期待一年后的蜕变吧！&lt;有同样想法的小伙伴，可以联系我一起交流学习哦！&gt;</p><ul><li><input checked="" disabled="" type="checkbox"> 🚩时间安排：预计7天更新完</li><li><input checked="" disabled="" type="checkbox"> 🎯开始时间：06-03</li><li><input checked="" disabled="" type="checkbox"> 🎉结束时间：06-17</li><li><input checked="" disabled="" type="checkbox"> 🍀总结：中间花了点时间学Vue，磨磨蹭蹭的终于完成了，配置太多了，一直在改bug的路上，前端比后端难！</li></ul><h2 id="一、项目概览"><a href="#一、项目概览" class="headerlink" title="一、项目概览"></a>一、项目概览</h2><blockquote><p>本文将实现一个基于 SSM-VUE 前后端分离的 CRUD 的小 Demo 来学习SSM整合，项目命名为SSM-VUE-CRUD</p></blockquote><p>项目地址：<a href="https://github.com/jwt1399/SSM-VUE-CRUD">https://github.com/jwt1399/SSM-VUE-CRUD</a></p><h3 id="相关技术"><a href="#相关技术" class="headerlink" title="相关技术"></a>相关技术</h3><h4 id="前端"><a href="#前端" class="headerlink" title="前端"></a>前端</h4><ul><li><p>开发框架：Vue3、Axios、Element Plus</p></li><li><p>脚手架：@Vue&#x2F;cli</p></li><li><p>依赖管理: npm</p></li><li><p>开发工具：VSCode 、Chrome</p></li></ul><h4 id="后端"><a href="#后端" class="headerlink" title="后端"></a>后端</h4><ul><li><p>开发框架：Spring、SpringMVC、Mybatis</p></li><li><p>数据库：MySQL</p></li><li><p>分页： pagehelper</p></li><li><p>逆向工程： Mybatis Generator</p></li><li><p>依赖管理： Maven</p></li><li><p>开发工具：IDEA 2022.1、Navicat 16</p></li></ul><h3 id="项目展示"><a href="#项目展示" class="headerlink" title="项目展示"></a>项目展示</h3><h4 id="1-员工列表"><a href="#1-员工列表" class="headerlink" title="1.员工列表"></a>1.员工列表</h4><p><img src="https://img.jwt1399.top/img/202206181715532.png"></p><h4 id="2-添加员工"><a href="#2-添加员工" class="headerlink" title="2.添加员工"></a>2.添加员工</h4><p><img src="https://img.jwt1399.top/img/202206181715024.png"></p><h4 id="3-修改员工"><a href="#3-修改员工" class="headerlink" title="3.修改员工"></a>3.修改员工</h4><p><img src="https://img.jwt1399.top/img/202206181716115.png"></p><h4 id="4-删除员工"><a href="#4-删除员工" class="headerlink" title="4.删除员工"></a>4.删除员工</h4><p><img src="https://img.jwt1399.top/img/202206181716034.png"></p><h4 id="5-批量删除员工"><a href="#5-批量删除员工" class="headerlink" title="5.批量删除员工"></a>5.批量删除员工</h4><p><img src="https://img.jwt1399.top/img/202206181716759.png"></p><h3 id="项目部署"><a href="#项目部署" class="headerlink" title="项目部署"></a>项目部署</h3><h4 id="后端部署"><a href="#后端部署" class="headerlink" title="后端部署"></a>后端部署</h4><h5 id="1-克隆本项目到本地"><a href="#1-克隆本项目到本地" class="headerlink" title="1.克隆本项目到本地"></a>1.克隆本项目到本地</h5><pre class="line-numbers language-bash"><code class="language-bash"><span class="token function">git</span> clone https://github.com/jwt1399/SSM-VUE-CRUD.git<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre><h5 id="2-创建数据库导入数据"><a href="#2-创建数据库导入数据" class="headerlink" title="2.创建数据库导入数据"></a>2.创建数据库导入数据</h5><p>先创建一个名为 ssm_crud 的数据库，然后再导入表数据 <code>ssm_crud.sql</code></p><h5 id="3-安装依赖"><a href="#3-安装依赖" class="headerlink" title="3.安装依赖"></a>3.安装依赖</h5><p>使用 IDEA 打开项目下的 ssm-crud-back ，等待 Maven 将 pom.xml 中的依赖下载完</p><h5 id="4-修改数据库配置"><a href="#4-修改数据库配置" class="headerlink" title="4.修改数据库配置"></a>4.修改数据库配置</h5><p>找到 src&#x2F;main&#x2F;resouces&#x2F;db.properties 修改成你自己的数据库配置</p><pre class="line-numbers language-properties"><code class="language-properties"><span class="token attr-name">jdbc.driver</span><span class="token punctuation">=</span><span class="token attr-value">com.mysql.cj.jdbc.Driver</span><span class="token attr-name">jdbc.url</span><span class="token punctuation">=</span><span class="token attr-value">jdbc:mysql://localhost:3306/ssm_crud?useUnicode=true&amp;characterEncoding=utf8</span><span class="token attr-name">jdbc.username</span><span class="token punctuation">=</span><span class="token attr-value">root</span><span class="token attr-name">jdbc.password</span><span class="token punctuation">=</span><span class="token attr-value">root</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre><h5 id="5-添加TomCat"><a href="#5-添加TomCat" class="headerlink" title="5.添加TomCat"></a>5.添加TomCat</h5><ul><li>点击 Add Configuration(添加配置) 进行配置， 点击 + 号，找到 Tomcat Service -&gt; Local(本地)</li><li>再点击 Tomcat 配置界面的 Deployment(部署)，再点击右下角的 fix ，选择 ssm-crud-back:war exploded，再将 Application context(应用程序上下文) 改为 &#x2F;ssm_crud_back</li><li>再将服务器 URL 改为”<code>http://localhost:8080/ssm_crud_back/</code>“，修改后应用即可</li></ul><h5 id="6-启动项目"><a href="#6-启动项目" class="headerlink" title="6.启动项目"></a>6.启动项目</h5><p>配置完 TomCat 后运行项目，浏览器会打开”<code>http://localhost:8080/ssm_crud_back/</code>“，显示“SSM-VUE-CRUD后端部署成功！！！”的文字，表示后端运行成功</p><h4 id="前端部署"><a href="#前端部署" class="headerlink" title="前端部署"></a>前端部署</h4><h5 id="1-安装依赖"><a href="#1-安装依赖" class="headerlink" title="1.安装依赖"></a>1.安装依赖</h5><p>使用 VScode 打开项目下的 ssm-crud-front，打开终端执行 <code>npm i</code> 下载依赖</p><h5 id="2-启动项目"><a href="#2-启动项目" class="headerlink" title="2.启动项目"></a>2.启动项目</h5><p>终端再执行 <code>npm run serve</code> 运行项目，编译完成后会浏览器会自动打开 “<code>http://0.0.0.0:8084/#/</code>“（可在vue.config.修改），当看到项目展示中的页面就说明部署成功啦。</p><h2 id="二、后端实现"><a href="#二、后端实现" class="headerlink" title="二、后端实现"></a>二、后端实现</h2><h3 id="1-环境搭建"><a href="#1-环境搭建" class="headerlink" title="1.环境搭建"></a>1.环境搭建</h3><h4 id="a-创建Maven项目"><a href="#a-创建Maven项目" class="headerlink" title="a.创建Maven项目"></a>a.创建Maven项目</h4><p>参考：<a href="https://www.cnblogs.com/l-y-h/p/11454933.html">IDEA创建maven web工程</a></p><h4 id="b-引入项目依赖jar包"><a href="#b-引入项目依赖jar包" class="headerlink" title="b.引入项目依赖jar包"></a>b.引入项目依赖jar包</h4><p>需要配置Spring，SpringMVC，mybatis，数据库连接池，数据库驱动包，以及其他的jar包，比如junit等。</p><pre class="line-numbers language-xml"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependencies</span><span class="token punctuation">></span></span>  <span class="token comment" spellcheck="true">&lt;!-- Spring WebMVC --></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-webmvc<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>5.3.20<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span>  <span class="token comment" spellcheck="true">&lt;!-- Spring JDBC --></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-jdbc<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>5.3.20<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>version</span><span class="token punctuation">></span></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>dependency</span><span class="token punctuation">></span></span>  <span class="token comment" spellcheck="true">&lt;!-- Spring Aspects --></span>  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>dependency</span><span class="token punctuation">></span></span>      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>groupId</span><span class="token punctuation">></span></span>org.springframework<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>groupId</span><span class="token punctuation">></span></span>      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>artifactId</span><span class="token punctuation">></span></span>spring-aspects<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>artifactId</span><span class="token punctuation">></span></span>      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>version</span><span class="token punctuation">></span></span>5.3.20<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>versio