<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>nerkeler</title>
  
  
  <link href="https://nerkeler.github.io/atom.xml" rel="self"/>
  
  <link href="https://nerkeler.github.io/"/>
  <updated>2025-05-25T07:20:04.080Z</updated>
  <id>https://nerkeler.github.io/</id>
  
  <author>
    <name>nerkeler</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>EasyExcel动态表头实现</title>
    <link href="https://nerkeler.github.io/2024/01/29/asyExcel%E5%8A%A8%E6%80%81%E8%A1%A8%E5%A4%B4%E5%AE%9E%E7%8E%B0/"/>
    <id>https://nerkeler.github.io/2024/01/29/asyExcel%E5%8A%A8%E6%80%81%E8%A1%A8%E5%A4%B4%E5%AE%9E%E7%8E%B0/</id>
    <published>2024-01-29T09:50:00.000Z</published>
    <updated>2025-05-25T07:20:04.080Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h2 id="EasyExcel动态表头实现"><a href="#EasyExcel动态表头实现" class="headerlink" title="EasyExcel动态表头实现"></a><code>EasyExcel</code>动态表头实现</h2><p>日常使用<code>EasyExcel</code>导出报表时，如果实体类字段较多，使用原始方式生成动态表头文件时会产生大量繁琐重复的代码，使用自定义注解和工具类处理可以大大简化代码开发</p><h4 id="简单实现例子"><a href="#简单实现例子" class="headerlink" title="简单实现例子"></a>简单实现例子</h4><p>准备一个实体类,注意<code>@ExcelColumnIgnore</code>注解为自定义注解，参数<code>branchLv</code> 表示当前机构层级</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DemoEntity</span>&#123;</span><br><span class="line">    <span class="meta">@ExcelColumnIgnore(branchLv = 2)</span></span><br><span class="line">    <span class="meta">@ExcelProperty(value = &#123;&quot;二级机构代码&quot;&#125;)</span></span><br><span class="line">    <span class="meta">@ApiModelProperty(value = &quot;二级机构代码&quot;,example = &quot;1110000&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">String</span> <span class="variable">organIdTwo</span> <span class="operator">=</span> <span class="string">&quot;-&quot;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@ExcelColumnIgnore(branchLv = 2)</span></span><br><span class="line">    <span class="meta">@ExcelProperty(value = &#123;&quot;二级机构名称&quot;&#125;)</span></span><br><span class="line">    <span class="meta">@ApiModelProperty(value = &quot;二级机构名称&quot;,example = &quot;北京市分公司&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">String</span> <span class="variable">organNameTwo</span> <span class="operator">=</span> <span class="string">&quot;-&quot;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@ExcelColumnIgnore(branchLv = 3)</span></span><br><span class="line">    <span class="meta">@ExcelProperty(value = &#123;&quot;三级机构代码&quot;&#125;)</span></span><br><span class="line">    <span class="meta">@ApiModelProperty(value = &quot;三级机构代码&quot;,example = &quot;1110100&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">String</span> <span class="variable">organIdThree</span> <span class="operator">=</span> <span class="string">&quot;-&quot;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@ExcelColumnIgnore(branchLv = 3)</span></span><br><span class="line">    <span class="meta">@ExcelProperty(value = &#123;&quot;三级机构名称&quot;&#125;)</span></span><br><span class="line">    <span class="meta">@ApiModelProperty(value = &quot;三级机构名称&quot;,example = &quot;北京市中心支公司本部&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">String</span> <span class="variable">organNameThree</span> <span class="operator">=</span> <span class="string">&quot;-&quot;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@ExcelColumnIgnore(branchLv = 4)</span></span><br><span class="line">    <span class="meta">@ExcelProperty(value = &#123;&quot;四级机构代码&quot;&#125;)</span></span><br><span class="line">    <span class="meta">@ApiModelProperty(value = &quot;四级机构代码&quot;,example = &quot;1110104&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">String</span> <span class="variable">organIdFour</span> <span class="operator">=</span> <span class="string">&quot;-&quot;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@ExcelColumnIgnore(branchLv = 4)</span></span><br><span class="line">    <span class="meta">@ExcelProperty(value = &#123;&quot;四级机构名称&quot;&#125;)</span></span><br><span class="line">    <span class="meta">@ApiModelProperty(value = &quot;四级机构名称&quot;,example = &quot;北京市朝阳区支公司&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">String</span> <span class="variable">organNameFour</span> <span class="operator">=</span> <span class="string">&quot;-&quot;</span>;</span><br><span class="line"></span><br><span class="line">    </span><br><span class="line">    <span class="meta">@ExcelProperty(value = &#123;&quot;查询时间&quot;&#125;)</span></span><br><span class="line">    <span class="meta">@ApiModelProperty(value = &quot;查询时间&quot;,example = &quot;2023-03-01至2023-06-26&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">String</span> <span class="variable">queryTime</span> <span class="operator">=</span> <span class="string">&quot;-&quot;</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@ExcelColumnIgnore</span></span><br><span class="line">    <span class="meta">@ExcelProperty(value = &#123;&quot;用户名&quot;&#125;)</span></span><br><span class="line">    <span class="meta">@ApiModelProperty(value = &quot;查询时间&quot;,example = &quot;2023-03-01至2023-06-26&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">String</span> <span class="variable">agentName</span> <span class="operator">=</span> <span class="string">&quot;-&quot;</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>直接生成Excel  </p><p><em>PS:  <code>EasyExcelUtils</code>   <code>ExcelTitleWriteHandler</code>   均在DEV环境，也可以从文章后看到具体实现</em></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">       <span class="type">String</span> <span class="variable">title</span> <span class="operator">=</span> <span class="string">&quot;测试标题&quot;</span>;</span><br><span class="line">       <span class="comment">// 展示层级</span></span><br><span class="line">       <span class="type">int</span> <span class="variable">showLv</span> <span class="operator">=</span> <span class="number">2</span>;</span><br><span class="line">       <span class="comment">// 展示层级排除字段</span></span><br><span class="line">       List&lt;String&gt; Strings = EasyExcelUtils.getExcludeColumnFiledList(DemoEntity.class, showLv);</span><br><span class="line"></span><br><span class="line">       List&lt;DemoEntity&gt; list = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">       list.add(<span class="keyword">new</span> <span class="title class_">DemoEntity</span>());</span><br><span class="line">       <span class="comment">// 生成Excel</span></span><br><span class="line">       EasyExcel.write(<span class="string">&quot;file.xlsx&quot;</span>, DemoEntity.class)</span><br><span class="line">               .excludeColumnFiledNames(Strings)</span><br><span class="line">               .sheet(<span class="string">&quot;title&quot;</span>)</span><br><span class="line">               .relativeHeadRowIndex(<span class="number">1</span>)</span><br><span class="line">               .registerWriteHandler(<span class="keyword">new</span> <span class="title class_">ExcelTitleWriteHandler</span>&lt;&gt;(title, DemoEntity.class, showLv))</span><br><span class="line">               .doWrite(list);</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">   &#125;</span><br></pre></td></tr></table></figure><p>生成的结果会跟随<code>showLv</code> 展示层级不同出现不同的列数，实现动态表头并且跟随字段控制。</p><p>这里用到的两个自定义实现类<code>EasyExcelUtils 、ExcelTitleWriteHandler</code>    ，和一个自定义注解<code>@ExcelColumnIgnore</code> 实现这个功能，背后的原理就是使用注解判断展示层级动态控制字段呈现</p><h4 id="ExcelColumnIgnore"><a href="#ExcelColumnIgnore" class="headerlink" title="@ExcelColumnIgnore"></a><code>@ExcelColumnIgnore</code></h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Retention(RetentionPolicy.RUNTIME)</span></span><br><span class="line"><span class="meta">@Target(ElementType.FIELD)</span></span><br><span class="line"><span class="keyword">public</span> <span class="meta">@interface</span> ExcelColumnIgnore &#123;</span><br><span class="line">    <span class="type">int</span> <span class="title function_">branchLv</span><span class="params">()</span> <span class="keyword">default</span> <span class="number">1000</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>ExcelColumnIgnore</code> 是一个自定义注解，只包含一个属性字段 <code>branchLv</code> 也就是标识当前字段的层级，默认1000是为了实现 无参数指定下 字段排除功能，也就是 在使用<code>ExcelColumnIgnore</code> 不指定参数 <code>branchLv</code> ,这个字段也将会被排除。</p><h4 id="EasyExcelUtils"><a href="#EasyExcelUtils" class="headerlink" title="EasyExcelUtils"></a><code>EasyExcelUtils</code></h4><p><code>EasyExcelUtils</code>  是一个自定义<code>Easyexcel</code>处理的工具类，<code>getExcludeColumnFiledList</code>方法就是配合<code>ExcelColumnIgnore</code>注解 动态获取需要排除的字段</p><h5 id="getExcludeColumnFiledList-Class-lt-T-gt-t"><a href="#getExcludeColumnFiledList-Class-lt-T-gt-t" class="headerlink" title="getExcludeColumnFiledList(Class&lt;T&gt; t)"></a><code>getExcludeColumnFiledList(Class&lt;T&gt; t)</code></h5><p>第一个方法需要指定实体类的类型，这样是为了获取当前实体类的所有字段和注解，这个方法默认排除所有带有<code>@ExcelColumnIgnore</code>  注解的字段</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 获取动态排除字段</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span>&lt;T&gt; List&lt;String&gt;   <span class="title function_">getExcludeColumnFiledList</span><span class="params">(Class&lt;T&gt; t)</span>&#123;</span><br><span class="line"></span><br><span class="line">    List&lt;String&gt; excludeColumnFiledList = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line"></span><br><span class="line">    Field[] declaredFields = t.getDeclaredFields();</span><br><span class="line">    <span class="keyword">for</span> (Field field : declaredFields) &#123;</span><br><span class="line"></span><br><span class="line">        Annotation[] declaredAnnotations = field.getDeclaredAnnotations();</span><br><span class="line">        <span class="keyword">for</span> (Annotation declaredAnnotation : declaredAnnotations) &#123;</span><br><span class="line"></span><br><span class="line">            <span class="comment">// 有 @ExcelColumnIgnore 表示要忽略当前字段计数 抵消 @ExcelProperty 的 +1</span></span><br><span class="line">            <span class="keyword">if</span>(declaredAnnotation <span class="keyword">instanceof</span> ExcelColumnIgnore)&#123;</span><br><span class="line">                excludeColumnFiledList.add(field.getName());</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> excludeColumnFiledList;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="getExcludeColumnFiledList-Class-lt-T-gt-t-int-showLv"><a href="#getExcludeColumnFiledList-Class-lt-T-gt-t-int-showLv" class="headerlink" title="getExcludeColumnFiledList(Class&lt;T&gt; t, int showLv)"></a><code>getExcludeColumnFiledList(Class&lt;T&gt; t, int showLv)</code></h5><p>重载方法增加了<code>showLv</code> 参数，表示要展示的层级，会保留 <code>@ExcelColumnIgnore</code>  注解上 <code>branchLv &lt;= showLv</code> 的字段</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span>&lt;T&gt; List&lt;String&gt;   <span class="title function_">getExcludeColumnFiledList</span><span class="params">(Class&lt;T&gt; t, <span class="type">int</span> showLv)</span>&#123;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    List&lt;String&gt; excludeColumnFiledList = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line"></span><br><span class="line">    Field[] declaredFields = t.getDeclaredFields();</span><br><span class="line">    <span class="keyword">for</span> (Field field : declaredFields) &#123;</span><br><span class="line"></span><br><span class="line">        Annotation[] declaredAnnotations = field.getDeclaredAnnotations();</span><br><span class="line">        <span class="keyword">for</span> (Annotation declaredAnnotation : declaredAnnotations) &#123;</span><br><span class="line"></span><br><span class="line">            <span class="comment">// 有 @ExcelColumnIgnore 表示要忽略当前字段计数 且branchLv &gt; showLv 抵消 @ExcelProperty 的 +1</span></span><br><span class="line">            <span class="keyword">if</span>(declaredAnnotation <span class="keyword">instanceof</span> ExcelColumnIgnore)&#123;</span><br><span class="line">                <span class="type">ExcelColumnIgnore</span> <span class="variable">annotation</span> <span class="operator">=</span> field.getAnnotation(ExcelColumnIgnore.class);</span><br><span class="line">                <span class="type">int</span> <span class="variable">branchLv</span> <span class="operator">=</span> annotation.branchLv();</span><br><span class="line">                <span class="keyword">if</span> (branchLv &gt; showLv)&#123;</span><br><span class="line">                    excludeColumnFiledList.add(field.getName());</span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">                &#125;</span><br><span class="line"></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> excludeColumnFiledList;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="ExcelTitleWriteHandler"><a href="#ExcelTitleWriteHandler" class="headerlink" title="ExcelTitleWriteHandler"></a><code>ExcelTitleWriteHandler</code></h4><p>这个方法是动态生成表头的实现类, 构造方法如下</p><ul><li><code>public ExcelTitleWriteHandler(String title,int column)</code> &#x2F;&#x2F;指定标题和列数</li><li><code> ExcelTitleWriteHandler(String title,Class&lt;T&gt; responseClass)</code>&#x2F;&#x2F; 指定标题和实体类（根据注解处理）</li><li><code>public ExcelTitleWriteHandler(String title,Class&lt;T&gt; responseClass,int showLv)</code>&#x2F;&#x2F; 指定标题、实体类、展示层级  （动态控制展示层级列数）</li></ul><p>代码如下</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.excel;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> cn.hutool.core.bean.BeanUtil;</span><br><span class="line"><span class="keyword">import</span> com.alibaba.excel.annotation.ExcelProperty;</span><br><span class="line"><span class="keyword">import</span> com.alibaba.excel.write.handler.SheetWriteHandler;</span><br><span class="line"><span class="keyword">import</span> com.alibaba.excel.write.metadata.holder.WriteSheetHolder;</span><br><span class="line"><span class="keyword">import</span> com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;</span><br><span class="line"><span class="keyword">import</span> com.aop.annotation.ExcelColumnIgnore;</span><br><span class="line"><span class="keyword">import</span> io.swagger.annotations.ApiModelProperty;</span><br><span class="line"><span class="keyword">import</span> org.apache.poi.ss.usermodel.*;</span><br><span class="line"><span class="keyword">import</span> org.apache.poi.ss.util.CellRangeAddress;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.lang.annotation.Annotation;</span><br><span class="line"><span class="keyword">import</span> java.lang.reflect.Field;</span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment"> * @description: Excel 表头生成器，指定动态标题 和 导出列数，生成对应表头内容</span></span><br><span class="line"><span class="comment"> *               如需表头样式自定义，可 添加属性  增加构造方法  动态修改当前样式设定 或 复制当前代码</span></span><br><span class="line"><span class="comment"> *               后续可补充内容  传入反参实体类，通过反射 自动计算当前类属性数量作为导出列数</span></span><br><span class="line"><span class="comment"> * @author: wcq</span></span><br><span class="line"><span class="comment"> * @date: 2023/11/28</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ExcelTitleWriteHandler</span>&lt;T&gt; <span class="keyword">implements</span> <span class="title class_">SheetWriteHandler</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 标题</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">String</span> <span class="variable">title</span> <span class="operator">=</span> <span class="string">&quot;&quot;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 多少列</span></span><br><span class="line">    <span class="keyword">private</span>  <span class="type">int</span> <span class="variable">column</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 实体类</span></span><br><span class="line">    <span class="keyword">private</span>  Class&lt;T&gt; responseClass = <span class="literal">null</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 统计表展示层级</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="variable">showLv</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">ExcelTitleWriteHandler</span><span class="params">(String title,<span class="type">int</span> column)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.title = title;</span><br><span class="line">        <span class="built_in">this</span>.column = column;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 用于明细表，注意字段必须要有 <span class="doctag">@ExcelProperty</span>注解</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> title</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> responseClass</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">ExcelTitleWriteHandler</span><span class="params">(String title,Class&lt;T&gt; responseClass)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.title = title;</span><br><span class="line">        <span class="built_in">this</span>.responseClass = responseClass;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 用于统计表，注意字段必须要有 <span class="doctag">@ExcelProperty</span>注解，</span></span><br><span class="line"><span class="comment">     * showLv 标识展示层级 比如 展示到四级，则 <span class="doctag">@ExcelColumnIgnore</span>(branchLv = 4)  branchLv &gt; 4 和默认注解将被忽略</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> title</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> responseClass</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">ExcelTitleWriteHandler</span><span class="params">(String title,Class&lt;T&gt; responseClass,<span class="type">int</span> showLv)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.title = title;</span><br><span class="line">        <span class="built_in">this</span>.responseClass = responseClass;</span><br><span class="line">        <span class="built_in">this</span>.showLv = showLv;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">beforeSheetCreate</span><span class="params">(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder)</span> &#123;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">afterSheetCreate</span><span class="params">(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder)</span> &#123;</span><br><span class="line"> </span><br><span class="line">        <span class="type">Workbook</span> <span class="variable">workbook</span> <span class="operator">=</span> writeWorkbookHolder.getWorkbook();</span><br><span class="line">        <span class="type">Sheet</span> <span class="variable">sheet</span> <span class="operator">=</span> workbook.getSheetAt(<span class="number">0</span>);</span><br><span class="line">        <span class="type">Row</span> <span class="variable">row1</span> <span class="operator">=</span> sheet.createRow(<span class="number">0</span>);</span><br><span class="line">        row1.setHeight((<span class="type">short</span>) <span class="number">800</span>);</span><br><span class="line">        <span class="type">Cell</span> <span class="variable">cell</span> <span class="operator">=</span> row1.createCell(<span class="number">0</span>);</span><br><span class="line"></span><br><span class="line">        <span class="comment">//设置标题</span></span><br><span class="line">        cell.setCellValue(title);</span><br><span class="line">        <span class="type">CellStyle</span> <span class="variable">cellStyle</span> <span class="operator">=</span> workbook.createCellStyle();</span><br><span class="line"></span><br><span class="line">        <span class="comment">//设置边框</span></span><br><span class="line">        cellStyle.setBorderBottom(BorderStyle.THIN);</span><br><span class="line">        cellStyle.setBorderLeft(BorderStyle.THIN);</span><br><span class="line">        cellStyle.setBorderRight(BorderStyle.THIN);</span><br><span class="line">        cellStyle.setBorderTop(BorderStyle.THIN);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 水平竖直居中</span></span><br><span class="line">        cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);</span><br><span class="line">        cellStyle.setAlignment(HorizontalAlignment.CENTER);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 灰色填充背景</span></span><br><span class="line">        cellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);</span><br><span class="line">        cellStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">        <span class="type">Font</span> <span class="variable">font</span> <span class="operator">=</span> workbook.createFont();</span><br><span class="line">        font.setBold(<span class="literal">true</span>);</span><br><span class="line">        font.setFontHeight((<span class="type">short</span>) <span class="number">400</span>);</span><br><span class="line">        font.setFontName(<span class="string">&quot;宋体&quot;</span>);</span><br><span class="line">        cellStyle.setFont(font);</span><br><span class="line">        cell.setCellStyle(cellStyle);</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">        <span class="comment">// 明细表处理</span></span><br><span class="line">        <span class="keyword">if</span>(column == <span class="number">0</span> )&#123;</span><br><span class="line">            Field[] declaredFields = responseClass.getDeclaredFields();</span><br><span class="line">            <span class="keyword">for</span> (Field field : declaredFields) &#123;</span><br><span class="line">                Annotation[] declaredAnnotations = field.getDeclaredAnnotations();</span><br><span class="line">                <span class="keyword">for</span> (Annotation declaredAnnotation : declaredAnnotations) &#123;</span><br><span class="line"></span><br><span class="line">                    <span class="comment">// 有 @ExcelProperty 表示一个有效字段</span></span><br><span class="line">                    <span class="keyword">if</span>(declaredAnnotation <span class="keyword">instanceof</span> ExcelProperty)</span><br><span class="line">                        column++;</span><br><span class="line"></span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">for</span> (Field declaredField : declaredFields) &#123;</span><br><span class="line">                Annotation[] declaredAnnotations = declaredField.getDeclaredAnnotations();</span><br><span class="line">                <span class="keyword">for</span> (Annotation declaredAnnotation : declaredAnnotations) &#123;</span><br><span class="line"></span><br><span class="line">                    <span class="comment">// 有 @ExcelColumnIgnore 表示要忽略当前字段计数  当branchLv &gt; show 时 抵消 @ExcelProperty 的 +1</span></span><br><span class="line">                    <span class="keyword">if</span>(declaredAnnotation <span class="keyword">instanceof</span> ExcelColumnIgnore) &#123;</span><br><span class="line">                        <span class="type">ExcelColumnIgnore</span> <span class="variable">annotation</span> <span class="operator">=</span> declaredField.getAnnotation(ExcelColumnIgnore.class);</span><br><span class="line"></span><br><span class="line">                        <span class="keyword">if</span> (showLv == <span class="number">0</span>)&#123;</span><br><span class="line">                            column--;</span><br><span class="line">                        &#125;  <span class="keyword">else</span> <span class="keyword">if</span> (showLv &gt; <span class="number">0</span> &amp;&amp; annotation.branchLv() &gt; showLv)&#123;</span><br><span class="line">                            column--;</span><br><span class="line">                        &#125;</span><br><span class="line"></span><br><span class="line">                    &#125;</span><br><span class="line"></span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">        sheet.addMergedRegionUnsafe(<span class="keyword">new</span> <span class="title class_">CellRangeAddress</span>(<span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, column-<span class="number">1</span>));</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>原理大同小异，配合注解可以动态处理字段的展示，从而获取准确的标题表头</p><h4 id="使用建议"><a href="#使用建议" class="headerlink" title="使用建议"></a>使用建议</h4><h5 id="明细表"><a href="#明细表" class="headerlink" title="明细表"></a>明细表</h5><p>明细表因为没有动态展示的功能,一般不需要使用<code>@ExcelColumnIgnore</code>注解，如果要使用<code>@ExcelColumnIgnore</code>时也不需要指定参数<code>branchLv</code></p><p><strong>步骤建议</strong></p><ol><li>获取导出实体类列表</li><li>导出时使用 <code>ExcelTitleWriteHandler</code>  动态生成表头，传入 （title class ），即可计算列数，或指定column 参数</li></ol><h5 id="统计表"><a href="#统计表" class="headerlink" title="统计表"></a>统计表</h5><p>统计表大部分需要根据不同机构层级展示，在实体类 中需要在各层级字段使用<code>@ExcelColumnIgnore</code>注解，并标注准确的<code>showLv</code>参数，</p><p><strong>步骤建议</strong></p><ol><li>实体类各层级字段使用<code>@ExcelColumnIgnore</code> 注解 二级机构 2、三级机构 3 、依次类推</li><li>获取导出实体类列表</li><li>使用<code>EasyExcelUtils</code> <code>getExcludeColumnFiledList(Class&lt;T&gt; t, int showLv)</code> 方法获取动态排除字段，注意传入展示层级</li><li>生成表头时<code>ExcelTitleWriteHandler</code> 使用<code>public ExcelTitleWriteHandler(String title,Class&lt;T&gt; responseClass,int showLv)</code> 构造方法，指定实体类和展示层级</li></ol>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;\assets\css\APlayer.min.css&quot;&gt;&lt;script src=&quot;\assets\js\APlayer.min.js&quot; cla</summary>
      
    
    
    
    <category term="Java" scheme="https://nerkeler.github.io/categories/Java/"/>
    
    
    <category term="excel" scheme="https://nerkeler.github.io/tags/excel/"/>
    
  </entry>
  
  <entry>
    <title>回溯经典案例</title>
    <link href="https://nerkeler.github.io/2024/01/04/%E5%9B%9E%E6%BA%AF%E7%BB%8F%E5%85%B8%E6%A1%88%E4%BE%8B/"/>
    <id>https://nerkeler.github.io/2024/01/04/%E5%9B%9E%E6%BA%AF%E7%BB%8F%E5%85%B8%E6%A1%88%E4%BE%8B/</id>
    <published>2024-01-04T02:19:00.000Z</published>
    <updated>2025-05-25T07:20:42.324Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h2 id="回溯经典案例"><a href="#回溯经典案例" class="headerlink" title="回溯经典案例"></a>回溯经典案例</h2><h4 id="全排列"><a href="#全排列" class="headerlink" title="全排列"></a>全排列</h4><p>全排列问题是回溯算法的一个典型应用。它的定义是在给定一个集合（如一个数组或字符串）的情况下，找 出这个集合中元素的所有可能的排列。</p><h5 id="question1"><a href="#question1" class="headerlink" title="question1"></a>question1</h5><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">输入一个整数数组，数组中不包含重复元素，返回所有可能的排列。</span></span><br></pre></td></tr></table></figure><p>例如：</p><ul><li>[1, 2] ：[1, 2], [2, 1]</li><li>[1, 2, 3] [1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]</li></ul><p>回溯代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">List&lt;Integer&gt; path = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">List&lt;List&lt;Integer&gt;&gt; result = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line"></span><br><span class="line">List&lt;List&lt;Integer&gt;&gt; <span class="title function_">permutationsI</span><span class="params">(<span class="type">int</span>[] nums)</span> &#123;</span><br><span class="line"></span><br><span class="line">    backtrack(nums);</span><br><span class="line">    <span class="keyword">return</span> result;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">backtrack</span><span class="params">( <span class="type">int</span>[] nums)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span>(path.size() &gt;= nums.length)&#123;</span><br><span class="line">        result.add(<span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;(path));</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> num : nums) &#123;</span><br><span class="line">        <span class="comment">// 排除重复元素 （剪枝）</span></span><br><span class="line">        <span class="keyword">if</span> (!path.contains(num)) &#123;</span><br><span class="line">            path.add(num);</span><br><span class="line">            backtrack(nums);</span><br><span class="line">            path.remove(path.size() - <span class="number">1</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里使用了列表去重，路径中如果包含当前元素表示已经有添加，直接跳过，这样就避免了  <code>111  122  133 .....</code> 等排列的形式出现</p><p>当然去重的逻辑不止一种，使用布尔值，set 都可以，这里就不展开，感兴趣可以自己尝试。</p><h4 id="子集"><a href="#子集" class="headerlink" title="子集"></a>子集</h4><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">给定一组不含重复元素的整数数组</span> <span class="string">nums，返回该数组所有可能的子集（幂集）。</span></span><br><span class="line"></span><br><span class="line"><span class="string">说明：解集不能包含重复的子集。</span></span><br></pre></td></tr></table></figure><p>例如：</p><ul><li>输入: <code>nums</code> &#x3D; [1,2,3] 输出: [ [3],  [1],  [2],  [1,2,3],  [1,3],  [2,3],  [1,2],  [] ]</li></ul><p>回溯代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> List&lt;Integer&gt; path = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> List&lt;List&lt;Integer&gt;&gt; result = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> List&lt;List&lt;Integer&gt;&gt; <span class="title function_">subsets</span><span class="params">(<span class="type">int</span>[] nums)</span> &#123;</span><br><span class="line">        bfs(<span class="number">0</span>, nums);</span><br><span class="line">        <span class="keyword">return</span> result;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">bfs</span><span class="params">(<span class="type">int</span> index, <span class="type">int</span>[] nums)</span> &#123;</span><br><span class="line">        </span><br><span class="line">        result.add(<span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;(path));</span><br><span class="line">        <span class="keyword">if</span> (index &gt;= nums.length) <span class="keyword">return</span>;</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> index; i &lt; nums.length; i++) &#123;</span><br><span class="line">            path.add(nums[i]);</span><br><span class="line">            bfs(i + <span class="number">1</span>, nums);</span><br><span class="line">            path.remove(path.size() - <span class="number">1</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="N皇后"><a href="#N皇后" class="headerlink" title="N皇后"></a>N皇后</h4><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">根据国际象棋的规则，皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。给定</span> <span class="string">𝑛</span></span><br><span class="line"><span class="string">个皇后和一个</span> <span class="string">𝑛</span> <span class="string">×</span> <span class="string">𝑛</span> <span class="string">大小的棋盘，寻找使得所有皇后之间无法相互攻击的摆放方案。</span></span><br></pre></td></tr></table></figure><p>八皇后问题可以看作是一个复合的回溯问题，比之一般的回溯问题的限定条件来说 十分复杂 放置每一个棋子（Q）时需要在 行、列、对角线 比较是否有冲突放置，而回溯的地方就是  放置的 ”Q“,  在不满足条件的时候需要回溯成原始的 ”.“</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Solution</span> &#123;</span><br><span class="line">    List&lt;List&lt;String&gt;&gt; res = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> List&lt;List&lt;String&gt;&gt; <span class="title function_">solveNQueens</span><span class="params">(<span class="type">int</span> n)</span> &#123;</span><br><span class="line">        <span class="type">char</span>[][] chessboard = <span class="keyword">new</span> <span class="title class_">char</span>[n][n];</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">char</span>[] c : chessboard) &#123;</span><br><span class="line">            Arrays.fill(c, <span class="string">&#x27;.&#x27;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        backTrack(n, <span class="number">0</span>, chessboard);</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">backTrack</span><span class="params">(<span class="type">int</span> n, <span class="type">int</span> row, <span class="type">char</span>[][] chessboard)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (row == n) &#123;</span><br><span class="line">            res.add(Array2List(chessboard));</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">col</span> <span class="operator">=</span> <span class="number">0</span>;col &lt; n; ++col) &#123;</span><br><span class="line">            <span class="keyword">if</span> (isValid (row, col, n, chessboard)) &#123;</span><br><span class="line">                chessboard[row][col] = <span class="string">&#x27;Q&#x27;</span>;</span><br><span class="line">                backTrack(n, row+<span class="number">1</span>, chessboard);</span><br><span class="line">                chessboard[row][col] = <span class="string">&#x27;.&#x27;</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> List <span class="title function_">Array2List</span><span class="params">(<span class="type">char</span>[][] chessboard)</span> &#123;</span><br><span class="line">        List&lt;String&gt; list = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line"></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">char</span>[] c : chessboard) &#123;</span><br><span class="line">            list.add(String.copyValueOf(c));</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> list;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isValid</span><span class="params">(<span class="type">int</span> row, <span class="type">int</span> col, <span class="type">int</span> n, <span class="type">char</span>[][] chessboard)</span> &#123;</span><br><span class="line">        <span class="comment">// 检查列</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i=<span class="number">0</span>; i&lt;row; ++i) &#123; <span class="comment">// 相当于剪枝</span></span><br><span class="line">            <span class="keyword">if</span> (chessboard[i][col] == <span class="string">&#x27;Q&#x27;</span>) &#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 检查45度对角线</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i=row-<span class="number">1</span>, j=col-<span class="number">1</span>; i&gt;=<span class="number">0</span> &amp;&amp; j&gt;=<span class="number">0</span>; i--, j--) &#123;</span><br><span class="line">            <span class="keyword">if</span> (chessboard[i][j] == <span class="string">&#x27;Q&#x27;</span>) &#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 检查135度对角线</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> i=row-<span class="number">1</span>, j=col+<span class="number">1</span>; i&gt;=<span class="number">0</span> &amp;&amp; j&lt;=n-<span class="number">1</span>; i--, j++) &#123;</span><br><span class="line">            <span class="keyword">if</span> (chessboard[i][j] == <span class="string">&#x27;Q&#x27;</span>) &#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;\assets\css\APlayer.min.css&quot;&gt;&lt;script src=&quot;\assets\js\APlayer.min.js&quot; cla</summary>
      
    
    
    
    <category term="算法" scheme="https://nerkeler.github.io/categories/%E7%AE%97%E6%B3%95/"/>
    
    
    <category term="算法" scheme="https://nerkeler.github.io/tags/%E7%AE%97%E6%B3%95/"/>
    
    <category term="Java" scheme="https://nerkeler.github.io/tags/Java/"/>
    
    <category term="回溯" scheme="https://nerkeler.github.io/tags/%E5%9B%9E%E6%BA%AF/"/>
    
  </entry>
  
  <entry>
    <title>回溯基础</title>
    <link href="https://nerkeler.github.io/2024/01/04/%E5%9B%9E%E6%BA%AF%E5%9F%BA%E7%A1%80/"/>
    <id>https://nerkeler.github.io/2024/01/04/%E5%9B%9E%E6%BA%AF%E5%9F%BA%E7%A1%80/</id>
    <published>2024-01-04T02:18:00.000Z</published>
    <updated>2024-01-04T02:26:13.322Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h2 id="回溯"><a href="#回溯" class="headerlink" title="回溯"></a>回溯</h2><h4 id="理论基础"><a href="#理论基础" class="headerlink" title="理论基础"></a>理论基础</h4><p>「回溯算法 backtracking algorithm」是一种通过穷举来解决问题的方法，它的核心思想是从一个初始状态出发，暴力搜索所有可能的解决方案，当遇到正确的解则将其记录，直到找到解或者尝试了所有可能的选择都无法找到解为止，所以回溯算法也通常采用“深度优先搜索”来遍历解空间。</p><h4 id="回溯法解决的问题"><a href="#回溯法解决的问题" class="headerlink" title="回溯法解决的问题"></a>回溯法解决的问题</h4><ul><li>组合问题：N个数里面按一定规则找出k个数的集合</li><li>切割问题：一个字符串按一定规则有几种切割方式</li><li>子集问题：一个N个数的集合里有多少符合条件的子集</li><li>排列问题：N个数按一定规则全排列，有几种排列方式</li><li>棋盘问题：N皇后，解数独等等</li></ul><h4 id="如何理解回溯法"><a href="#如何理解回溯法" class="headerlink" title="如何理解回溯法"></a>如何理解回溯法</h4><p><strong>回溯法解决的问题都可以抽象为树形结构</strong>，<strong>集合的大小就构成了树的宽度，递归的深度，都构成的树的深度</strong>。</p><h4 id="二叉树例子"><a href="#二叉树例子" class="headerlink" title="二叉树例子"></a>二叉树例子</h4><h5 id="题目"><a href="#题目" class="headerlink" title="题目"></a>题目</h5><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">给定一个二叉树，搜索并记录所有值为 7 的节点，请返回节点列表</span><br></pre></td></tr></table></figure><p><img src="https://pic.imgdb.cn/item/6565a520c458853aef6e2cb0.png" alt="img"></p><h5 id="题解"><a href="#题解" class="headerlink" title="题解"></a>题解</h5><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br><span class="line">224</span><br><span class="line">225</span><br><span class="line">226</span><br><span class="line">227</span><br><span class="line">228</span><br><span class="line">229</span><br><span class="line">230</span><br><span class="line">231</span><br><span class="line">232</span><br><span class="line">233</span><br><span class="line">234</span><br><span class="line">235</span><br><span class="line">236</span><br><span class="line">237</span><br></pre></td><td class="code"><pre><span class="line">## 回溯</span><br><span class="line"></span><br><span class="line">#### 理论基础</span><br><span class="line"></span><br><span class="line">「回溯算法 backtracking algorithm」是一种通过穷举来解决问题的方法，它的核心思想是从一个初始状态出发，暴力搜索所有可能的解决方案，当遇到正确的解则将其记录，直到找到解或者尝试了所有可能的选择都无法找到解为止，所以回溯算法也通常采用“深度优先搜索”来遍历解空间。</span><br><span class="line"></span><br><span class="line">#### 回溯法解决的问题</span><br><span class="line"></span><br><span class="line">- 组合问题：N个数里面按一定规则找出k个数的集合</span><br><span class="line">- 切割问题：一个字符串按一定规则有几种切割方式</span><br><span class="line">- 子集问题：一个N个数的集合里有多少符合条件的子集</span><br><span class="line">- 排列问题：N个数按一定规则全排列，有几种排列方式</span><br><span class="line">- 棋盘问题：N皇后，解数独等等</span><br><span class="line"></span><br><span class="line">#### 如何理解回溯法</span><br><span class="line"></span><br><span class="line">**回溯法解决的问题都可以抽象为树形结构**，**集合的大小就构成了树的宽度，递归的深度，都构成的树的深度**。</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">#### 二叉树例子</span><br><span class="line"></span><br><span class="line">##### 题目</span><br><span class="line"></span><br><span class="line">```css</span><br><span class="line">给定一个二叉树，搜索并记录所有值为 7 的节点，请返回节点列表</span><br><span class="line">```</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">![](https://pic.imgdb.cn/item/6565a520c458853aef6e2cb0.png)</span><br><span class="line"></span><br><span class="line">##### 题解</span><br><span class="line"></span><br><span class="line">```java</span><br><span class="line">/* 前序遍历： */</span><br><span class="line">void preOrder(TreeNode root) &#123;</span><br><span class="line">    // 递归到终止节点</span><br><span class="line">    if (root == null) &#123;</span><br><span class="line">        return;</span><br><span class="line">    &#125;</span><br><span class="line">    if (root.val == 7) &#123;</span><br><span class="line">        // 记录解</span><br><span class="line">        res.add(root);</span><br><span class="line">    &#125;</span><br><span class="line">    preOrder(root.left);</span><br><span class="line">    preOrder(root.right);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">```</span><br><span class="line"></span><br><span class="line">此时，我们遍历的顺序就是  ` 1-3-2-5-4-7-6`  ，也可以从下图中看出。</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">![](https://pic.imgdb.cn/item/6565a4dec458853aef6d60f0.png)</span><br><span class="line"></span><br><span class="line">#### 简单组合例子</span><br><span class="line"></span><br><span class="line">```yaml</span><br><span class="line">给定两个整数 n 和 k，返回范围 [1, n] 中所有可能的 k 个数的组合。</span><br><span class="line">```</span><br><span class="line"></span><br><span class="line">例如  `n = 4、 k = 3`  ，相当于我们在 `[1,2,3,4`]中取 `3` 个数组合 则图解应为</span><br><span class="line"></span><br><span class="line">![](https://pic.imgdb.cn/item/6565a9cdc458853aef7ba00b.png)</span><br><span class="line"></span><br><span class="line">代码：</span><br><span class="line"></span><br><span class="line">```java</span><br><span class="line">    private List&lt;List&lt;Integer&gt;&gt; result = new ArrayList&lt;&gt;();</span><br><span class="line">    private List&lt;Integer&gt; path = new ArrayList&lt;&gt;();</span><br><span class="line"></span><br><span class="line">    public List&lt;List&lt;Integer&gt;&gt; combine(int n, int k) &#123;</span><br><span class="line">        int[] ints = new int[n];</span><br><span class="line">        for (int i = 1; i &lt;= n; i++) &#123;</span><br><span class="line">            ints[i-1] = i;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        dfs(0,k,ints);</span><br><span class="line"></span><br><span class="line">        return result;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">    private void dfs(int start,int k,int[] ints)&#123;</span><br><span class="line"></span><br><span class="line">        if(path.size() == k)</span><br><span class="line">            result.add(new ArrayList&lt;&gt;(path));</span><br><span class="line"></span><br><span class="line">        if(start &gt;= ints.length || path.size() &gt;= k) return;</span><br><span class="line"></span><br><span class="line">        for (int i = start; i &lt; ints.length; i++) &#123;</span><br><span class="line">            path.add(ints[i]);</span><br><span class="line">            dfs(i+1,k,ints);</span><br><span class="line">            path.remove(path.size()-1);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">```</span><br><span class="line"></span><br><span class="line">代码暂且不管，从图中可以看到，我们将这个问题转化为一个树形遍历， 将回溯问题 转换为 数层遍历有助于我们理解回溯的过程。</span><br><span class="line"></span><br><span class="line">#### 尝试与回退</span><br><span class="line"></span><br><span class="line">**之所以称之为回溯算法，是因为该算法在搜索解空间时会采用“尝试”与“回退”的策略**。当算法在搜索过程中遇到某个状态无法继续前进或无法得到满足条件的解时，它会撤销上一步的选择，退回到之前的状态，并尝试其他可能的选择。</span><br><span class="line"></span><br><span class="line">##### 尝试</span><br><span class="line"></span><br><span class="line">在进入递归后紧接着就是 逻辑判断 的 `return` 语句，它会在尝试失败后返回到上一层递归，从树形结构理解就是返回到了它的父节点。</span><br><span class="line"></span><br><span class="line">##### 回退</span><br><span class="line"></span><br><span class="line">通常使用`path` 记录递归调用的路径，也就是大部分情况下的符合条件的解，当递归返回时，路径也要相应的回退</span><br><span class="line"></span><br><span class="line">例如上述图解，我们在 `[1-2-3]` 得到一个满足的解后，程序 `return`, 这时需要弹出`path`中最后一个元素，也就是 使递归层 回到  `[1-2]`</span><br><span class="line"></span><br><span class="line">##### 例子</span><br><span class="line"></span><br><span class="line">在上述的二叉树例题中，如果要获取节点的路径呢？</span><br><span class="line"></span><br><span class="line">```yaml</span><br><span class="line">在二叉树中搜索所有值为 7 的节点，请返回根节点到这些节点的路径。</span><br><span class="line">```</span><br><span class="line"></span><br><span class="line">题解：</span><br><span class="line"></span><br><span class="line">```java</span><br><span class="line">/* 前序遍历：例题二 */</span><br><span class="line">void preOrder(TreeNode root) &#123;</span><br><span class="line">    if (root == null) &#123;</span><br><span class="line">        return;</span><br><span class="line">    &#125;</span><br><span class="line">    // 尝试</span><br><span class="line">    path.add(root);</span><br><span class="line">    if (root.val == 7) &#123;</span><br><span class="line">        // 记录解</span><br><span class="line">        res.add(new ArrayList&lt;&gt;(path));</span><br><span class="line">    &#125;</span><br><span class="line">    preOrder(root.left);</span><br><span class="line">    preOrder(root.right);</span><br><span class="line">    // 回退</span><br><span class="line">    path.remove(path.size() - 1);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">```</span><br><span class="line"></span><br><span class="line">**`path` ：路径，也就是满足条件的一条解。**</span><br><span class="line"></span><br><span class="line">#### 剪枝</span><br><span class="line"></span><br><span class="line">复杂的回溯问题通常包含一个或多个约束条件，**约束条件通常可用于“剪枝”**。</span><br><span class="line"></span><br><span class="line">```yaml</span><br><span class="line">在二叉树中搜索所有值为 7 的节点，请返回根节点到这些节点的路径，**并要求路径中不包含值为 3 的节点**。</span><br><span class="line">```</span><br><span class="line"></span><br><span class="line">这样的话，我们就需要剪枝，简单来说，就是增加递归的限定条件，不满足条件的路径会提前弹出</span><br><span class="line"></span><br><span class="line">图示：</span><br><span class="line"></span><br><span class="line">![](https://pic.imgdb.cn/item/6565b200c458853aef9446af.png)</span><br><span class="line"></span><br><span class="line">代码：</span><br><span class="line"></span><br><span class="line">```java</span><br><span class="line">void preOrder(TreeNode root) &#123;</span><br><span class="line">    // 剪枝</span><br><span class="line">    if (root == null || root.val == 3) &#123;</span><br><span class="line">        return;</span><br><span class="line">    &#125;</span><br><span class="line">    // 尝试</span><br><span class="line">    path.add(root);</span><br><span class="line">    if (root.val == 7) &#123;</span><br><span class="line">        // 记录解</span><br><span class="line">        res.add(new ArrayList&lt;&gt;(path));</span><br><span class="line">    &#125;</span><br><span class="line">    preOrder(root.left);</span><br><span class="line">    preOrder(root.right);</span><br><span class="line">    // 回退</span><br><span class="line">    path.remove(path.size() - 1);</span><br><span class="line">&#125;</span><br><span class="line">```</span><br><span class="line"></span><br><span class="line">剪枝操作不止在限定条件中，如 **简单组合例子** 中，横向遍历了 第一层 数值， 在下面的递归层级中，`start`都增加了 `1`</span><br><span class="line"></span><br><span class="line">```java</span><br><span class="line"> for (int i = start; i &lt; ints.length; i++) &#123;</span><br><span class="line">            path.add(ints[i]);</span><br><span class="line">            dfs(i+1,k,ints);</span><br><span class="line">            path.remove(path.size()-1);</span><br><span class="line">       &#125;</span><br><span class="line">```</span><br><span class="line"></span><br><span class="line">这样 下一层级节点就从 `i+1`开始，这样就减少了  当前值的层级，相当于剪枝。</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">#### 框架代码</span><br><span class="line"></span><br><span class="line">```java</span><br><span class="line">// 结果集</span><br><span class="line">private List  result;</span><br><span class="line">// 路径</span><br><span class="line">private List path</span><br><span class="line"></span><br><span class="line">/* 回溯算法框架 */</span><br><span class="line">void backtrack(int start, List&lt;Choice&gt; choices,) &#123;</span><br><span class="line">    // 判断是否为解</span><br><span class="line">    if (isSolution(start)) &#123;</span><br><span class="line">        // 记录解</span><br><span class="line">       result.add(path)</span><br><span class="line">        // 停止继续搜索</span><br><span class="line">        return;</span><br><span class="line">    &#125;</span><br><span class="line">    // 遍历所有选择</span><br><span class="line">    for (Choice choice : choices) &#123;</span><br><span class="line">        // 剪枝：判断选择是否合法</span><br><span class="line">        if (isValid(state, choice)) &#123;</span><br><span class="line">            // 尝试：做出选择，更新状态</span><br><span class="line">            makeChoice(state, choice);</span><br><span class="line">            backtrack(state, choices, res);</span><br><span class="line">            // 回退：撤销选择，恢复到之前的状态</span><br><span class="line">            undoChoice(state, choice);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">```</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">#### 参考</span><br><span class="line"></span><br><span class="line">[HELLO 算法](https://www.hello-algo.com/chapter_backtracking/backtracking_algorithm/#1316)</span><br><span class="line"></span><br><span class="line">[代码随想录](https://www.programmercarl.com/%E5%9B%9E%E6%BA%AF%E7%AE%97%E6%B3%95%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%80.html#%E9%A2%98%E7%9B%AE%E5%88%86%E7%B1%BB)</span><br></pre></td></tr></table></figure><p>此时，我们遍历的顺序就是 <code> 1-3-2-5-4-7-6</code> ，也可以从下图中看出。</p><p><img src="https://pic.imgdb.cn/item/6565a4dec458853aef6d60f0.png" alt="img"></p><h4 id="简单组合例子"><a href="#简单组合例子" class="headerlink" title="简单组合例子"></a>简单组合例子</h4><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">给定两个整数 n 和 k，返回范围 [1, n] 中所有可能的 k 个数的组合。</span><br></pre></td></tr></table></figure><p>例如 <code>n = 4、 k = 3</code> ，相当于我们在 <code>[1,2,3,4</code>]中取 <code>3</code> 个数组合 则图解应为</p><p><img src="https://pic.imgdb.cn/item/6565a9cdc458853aef7ba00b.png" alt="img"></p><p>代码：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line">private List&lt;List&lt;Integer&gt;&gt; result = new ArrayList&lt;&gt;();</span><br><span class="line">private List&lt;Integer&gt; path = new ArrayList&lt;&gt;();</span><br><span class="line"></span><br><span class="line">public List&lt;List&lt;Integer&gt;&gt; combine(int n, int k) &#123;</span><br><span class="line">    int[] ints = new int[n];</span><br><span class="line">    for (int i = 1; i &lt;= n; i++) &#123;</span><br><span class="line">        ints[i-1] = i;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    dfs(0,k,ints);</span><br><span class="line"></span><br><span class="line">    return result;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line">private void dfs(int start,int k,int[] ints)&#123;</span><br><span class="line"></span><br><span class="line">    if(path.size() == k)</span><br><span class="line">        result.add(new ArrayList&lt;&gt;(path));</span><br><span class="line"></span><br><span class="line">    if(start &gt;= ints.length || path.size() &gt;= k) return;</span><br><span class="line"></span><br><span class="line">    for (int i = start; i &lt; ints.length; i++) &#123;</span><br><span class="line">        path.add(ints[i]);</span><br><span class="line">        dfs(i+1,k,ints);</span><br><span class="line">        path.remove(path.size()-1);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>代码暂且不管，从图中可以看到，我们将这个问题转化为一个树形遍历， 将回溯问题 转换为 数层遍历有助于我们理解回溯的过程。</p><h4 id="尝试与回退"><a href="#尝试与回退" class="headerlink" title="尝试与回退"></a>尝试与回退</h4><p><strong>之所以称之为回溯算法，是因为该算法在搜索解空间时会采用“尝试”与“回退”的策略</strong>。当算法在搜索过程中遇到某个状态无法继续前进或无法得到满足条件的解时，它会撤销上一步的选择，退回到之前的状态，并尝试其他可能的选择。</p><h5 id="尝试"><a href="#尝试" class="headerlink" title="尝试"></a>尝试</h5><p>在进入递归后紧接着就是 逻辑判断 的 <code>return</code> 语句，它会在尝试失败后返回到上一层递归，从树形结构理解就是返回到了它的父节点。</p><h5 id="回退"><a href="#回退" class="headerlink" title="回退"></a>回退</h5><p>通常使用<code>path</code> 记录递归调用的路径，也就是大部分情况下的符合条件的解，当递归返回时，路径也要相应的回退</p><p>例如上述图解，我们在 <code>[1-2-3]</code> 得到一个满足的解后，程序 <code>return</code>, 这时需要弹出<code>path</code>中最后一个元素，也就是 使递归层 回到 <code>[1-2]</code></p><h5 id="例子"><a href="#例子" class="headerlink" title="例子"></a>例子</h5><p>在上述的二叉树例题中，如果要获取节点的路径呢？</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">在二叉树中搜索所有值为 7 的节点，请返回根节点到这些节点的路径。</span><br></pre></td></tr></table></figure><p>题解：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">/* 前序遍历：例题二 */</span><br><span class="line">void preOrder(TreeNode root) &#123;</span><br><span class="line">    if (root == null) &#123;</span><br><span class="line">        return;</span><br><span class="line">    &#125;</span><br><span class="line">    // 尝试</span><br><span class="line">    path.add(root);</span><br><span class="line">    if (root.val == 7) &#123;</span><br><span class="line">        // 记录解</span><br><span class="line">        res.add(new ArrayList&lt;&gt;(path));</span><br><span class="line">    &#125;</span><br><span class="line">    preOrder(root.left);</span><br><span class="line">    preOrder(root.right);</span><br><span class="line">    // 回退</span><br><span class="line">    path.remove(path.size() - 1);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong><code>path</code> ：路径，也就是满足条件的一条解。</strong></p><h4 id="剪枝"><a href="#剪枝" class="headerlink" title="剪枝"></a>剪枝</h4><p>复杂的回溯问题通常包含一个或多个约束条件，<strong>约束条件通常可用于“剪枝”</strong>。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">在二叉树中搜索所有值为 7 的节点，请返回根节点到这些节点的路径，**并要求路径中不包含值为 3 的节点**。</span><br></pre></td></tr></table></figure><p>这样的话，我们就需要剪枝，简单来说，就是增加递归的限定条件，不满足条件的路径会提前弹出</p><p>图示：</p><p><img src="https://pic.imgdb.cn/item/6565b200c458853aef9446af.png" alt="img"></p><p>代码：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">void preOrder(TreeNode root) &#123;</span><br><span class="line">    // 剪枝</span><br><span class="line">    if (root == null || root.val == 3) &#123;</span><br><span class="line">        return;</span><br><span class="line">    &#125;</span><br><span class="line">    // 尝试</span><br><span class="line">    path.add(root);</span><br><span class="line">    if (root.val == 7) &#123;</span><br><span class="line">        // 记录解</span><br><span class="line">        res.add(new ArrayList&lt;&gt;(path));</span><br><span class="line">    &#125;</span><br><span class="line">    preOrder(root.left);</span><br><span class="line">    preOrder(root.right);</span><br><span class="line">    // 回退</span><br><span class="line">    path.remove(path.size() - 1);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>剪枝操作不止在限定条件中，如 <strong>简单组合例子</strong> 中，横向遍历了 第一层 数值， 在下面的递归层级中，<code>start</code>都增加了 <code>1</code></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">for (int i = start; i &lt; ints.length; i++) &#123;</span><br><span class="line">           path.add(ints[i]);</span><br><span class="line">           dfs(i+1,k,ints);</span><br><span class="line">           path.remove(path.size()-1);</span><br><span class="line">      &#125;</span><br></pre></td></tr></table></figure><p>这样 下一层级节点就从 <code>i+1</code>开始，这样就减少了 当前值的层级，相当于剪枝。</p><h4 id="框架代码"><a href="#框架代码" class="headerlink" title="框架代码"></a>框架代码</h4><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line">// 结果集</span><br><span class="line">private List  result;</span><br><span class="line">// 路径</span><br><span class="line">private List path</span><br><span class="line"></span><br><span class="line">/* 回溯算法框架 */</span><br><span class="line">void backtrack(int start, List&lt;Choice&gt; choices,) &#123;</span><br><span class="line">    // 判断是否为解</span><br><span class="line">    if (isSolution(start)) &#123;</span><br><span class="line">        // 记录解</span><br><span class="line">       result.add(path)</span><br><span class="line">        // 停止继续搜索</span><br><span class="line">        return;</span><br><span class="line">    &#125;</span><br><span class="line">    // 遍历所有选择</span><br><span class="line">    for (Choice choice : choices) &#123;</span><br><span class="line">        // 剪枝：判断选择是否合法</span><br><span class="line">        if (isValid(state, choice)) &#123;</span><br><span class="line">            // 尝试：做出选择，更新状态</span><br><span class="line">            makeChoice(state, choice);</span><br><span class="line">            backtrack(state, choices, res);</span><br><span class="line">            // 回退：撤销选择，恢复到之前的状态</span><br><span class="line">            undoChoice(state, choice);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h4><p><a href="https://www.hello-algo.com/chapter_backtracking/backtracking_algorithm/#1316">HELLO 算法</a></p><p><a href="https://www.programmercarl.com/%E5%9B%9E%E6%BA%AF%E7%AE%97%E6%B3%95%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%80.html#%E9%A2%98%E7%9B%AE%E5%88%86%E7%B1%BB">代码随想录</a></p>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;\assets\css\APlayer.min.css&quot;&gt;&lt;script src=&quot;\assets\js\APlayer.min.js&quot; cla</summary>
      
    
    
    
    <category term="算法" scheme="https://nerkeler.github.io/categories/%E7%AE%97%E6%B3%95/"/>
    
    
    <category term="算法" scheme="https://nerkeler.github.io/tags/%E7%AE%97%E6%B3%95/"/>
    
    <category term="Java" scheme="https://nerkeler.github.io/tags/Java/"/>
    
  </entry>
  
  <entry>
    <title>SpringSecurity表单认证（一）</title>
    <link href="https://nerkeler.github.io/2023/08/02/SpringSecurity%E8%A1%A8%E5%8D%95%E8%AE%A4%E8%AF%81%EF%BC%88%E4%B8%80%EF%BC%89/"/>
    <id>https://nerkeler.github.io/2023/08/02/SpringSecurity%E8%A1%A8%E5%8D%95%E8%AE%A4%E8%AF%81%EF%BC%88%E4%B8%80%EF%BC%89/</id>
    <published>2023-08-02T06:55:00.000Z</published>
    <updated>2024-01-04T01:39:41.426Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h1 id="SpringSecurity表单认证（一）"><a href="#SpringSecurity表单认证（一）" class="headerlink" title="SpringSecurity表单认证（一）"></a>SpringSecurity表单认证（一）</h1><h2 id="新建一个SpringSecutiry项目"><a href="#新建一个SpringSecutiry项目" class="headerlink" title="新建一个SpringSecutiry项目"></a>新建一个SpringSecutiry项目</h2><p>创建一个新项目，名称任意</p><p><img src="https://pic.imgdb.cn/item/64c861f01ddac507cc1a539b.png"></p><p>pom文件引入依赖，版本任意。</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependencies</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-web<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">version</span>&gt;</span>2.3.3.RELEASE<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-security<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">version</span>&gt;</span>2.3.3.RELEASE<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependencies</span>&gt;</span></span><br></pre></td></tr></table></figure><p>简略设置一下，启动文件</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DemoApplication</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        SpringApplication.run(DemoApplication.class, args);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="meta">@GetMapping(&quot;/hello&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">hello</span><span class="params">(<span class="meta">@RequestParam(value = &quot;name&quot;, defaultValue = &quot;World&quot;)</span> String name)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> String.format(<span class="string">&quot;Hello %s!&quot;</span>, name);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>此时访问任意路径都会被拦截到  &#x2F;login 路径，呈现以下登录页面</p><p><img src="https://pic.imgdb.cn/item/64c865d91ddac507cc21bc69.png"></p><p>到此为止，就已经新建了一个SpringSecutiry最基本的demo</p><h3 id="思考"><a href="#思考" class="headerlink" title="思考"></a>思考</h3><p>springsecutity作为重量级的安全框架（相对于shiro)，搭建起来却并没有很繁琐，其实这归功于springboot自动配置帮我们节省了繁琐的配置，springsecurity提供了很多强大的功能，但最核心的当属两方面</p><ul><li><strong>认证</strong> <strong>Authentication</strong></li><li><strong>授权</strong> <strong>Authorization</strong></li></ul><p>通俗来说，认证就是你是谁，授权就是你可以作什么</p><h2 id="简述认证"><a href="#简述认证" class="headerlink" title="简述认证"></a>简述认证</h2><p>springsecutiry支持多种不同的认证方式</p><ul><li>表单认证</li><li>OAuth2.0认证</li><li>SAML2.0认证</li><li>CAS2.0认证</li><li>RememberMe 自动认证</li><li>JAAS认证</li><li>OpenID 去中心化认证</li><li>Pre-Authentication Scenarios 认证</li><li>X509 认证</li><li>HTTP Basic认证</li><li>HTTP Digest认证</li></ul><h3 id="认证接口"><a href="#认证接口" class="headerlink" title="认证接口"></a>认证接口</h3><p>认证信息主要的操作功能由Authentication 定义</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">Authentication</span> <span class="keyword">extends</span> <span class="title class_">Principal</span>, Serializable &#123;</span><br><span class="line">    Collection&lt;? <span class="keyword">extends</span> <span class="title class_">GrantedAuthority</span>&gt; getAuthorities();</span><br><span class="line"></span><br><span class="line">    Object <span class="title function_">getCredentials</span><span class="params">()</span>;</span><br><span class="line"></span><br><span class="line">    Object <span class="title function_">getDetails</span><span class="params">()</span>;</span><br><span class="line"></span><br><span class="line">    Object <span class="title function_">getPrincipal</span><span class="params">()</span>;</span><br><span class="line"></span><br><span class="line">    <span class="type">boolean</span> <span class="title function_">isAuthenticated</span><span class="params">()</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">setAuthenticated</span><span class="params">(<span class="type">boolean</span> var1)</span> <span class="keyword">throws</span> IllegalArgumentException;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li>getAuthorities   获取用户权限</li><li>getCredentials 获取用户凭证，一般来说就是密码</li><li>getDetails 获取用户携带的详细信息  请求路径等</li><li>getPrincipal 获取当前用户 </li><li>isAuthenticated  是否认证成功</li></ul><p>大致看一下Authentication 的子类实现，可以看到不同的认证方式会有不同的实现类，这里先不展开说明，大致有个印象</p><p><img src="https://pic.imgdb.cn/item/64c86f121ddac507cc35078a.png"></p><p>认证信息由Authentication和其子类提供，而认证工作则是由AuthenticationManager接口极其子类实现</p><p>AuthenticationManager接口只提供了一个认证方法。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">AuthenticationManager</span> &#123;</span><br><span class="line">    Authentication <span class="title function_">authenticate</span><span class="params">(Authentication var1)</span> <span class="keyword">throws</span> AuthenticationException;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>不同的子类也代表了不同的认证方式，只需要有个印象就够了，后面碰到会详细说明</p><p><img src="https://pic.imgdb.cn/item/64c871be1ddac507cc3b3a1a.png"></p><h2 id="过滤器链"><a href="#过滤器链" class="headerlink" title="过滤器链"></a>过滤器链</h2><p>在Spring Security中，认证和授权等功能都是基于过滤器完成，Spring Securiy常见的过滤器如下</p><table><thead><tr><th>过滤器</th><th>过滤器作用</th><th>预加载</th></tr></thead><tbody><tr><td>ChannelProcessingFilter</td><td>过滤请求协议，HTTPS&#x2F;HTTP</td><td>NO</td></tr><tr><td>WebAsyncManagerIntegrationFilter</td><td>将WebAsyncManager 与Spring Security 上下文进行集成</td><td>YES</td></tr><tr><td>SecurityContextPersistenceFilter</td><td>在处理请求前，将安全信息加载到SecurityContextHolder中方便后续使用，请求结束后，在擦除掉信息</td><td>YES</td></tr><tr><td>HeaderWriterFilter</td><td>头信息加入到相应中</td><td>YES</td></tr><tr><td>CorsFilter</td><td>处理跨域问题</td><td>NO</td></tr><tr><td>Csffilter</td><td>处理CSRF攻击</td><td>YES</td></tr><tr><td>LogoutFilter</td><td>处理注销登录</td><td>YES</td></tr><tr><td>OAuth2AuthorizationRequestRedirectFilter</td><td>处理OAuth2认证重定向</td><td>NO</td></tr><tr><td>Saml2WebSsoAuthenticationRequestFilter</td><td>处理SAML认证</td><td>NO</td></tr><tr><td>X509AuthenticationFilter</td><td>处理X509认证</td><td>NO</td></tr><tr><td>AbstractPreAuthenticatedProcessingFilter</td><td>处理预认证问题</td><td>NO</td></tr><tr><td>CasAuthenticationFilter</td><td>处理CAS单点登录</td><td>NO</td></tr><tr><td>OAuth2LoginAuthenticationFilter</td><td>处理OAuth2</td><td>NO</td></tr><tr><td>Saml2WebSsoAuthenticationFilter</td><td>处理SAML认证</td><td>NO</td></tr><tr><td>UsenamePasswordAuthenticationFilter</td><td>处理表单登录</td><td>YES</td></tr><tr><td>OpenIDAuthenticationFilter</td><td>处理OpenID认证</td><td>NO</td></tr><tr><td>DefaultLoginPageGenemtingFilter</td><td>配置默认登录页面</td><td>YES</td></tr><tr><td>DefaultLogoutPageGeneratingFilter</td><td>配置默认注销页面</td><td>YES</td></tr><tr><td>ConcurentSessionFilter</td><td>处理Session有效期</td><td>NO</td></tr><tr><td>DigestAuthenticationFilter</td><td>处理HTTP摘要认证</td><td>NO</td></tr><tr><td>BearerTokenAuthenticationFilter</td><td>处理OAuth2认证时的Access Token</td><td>NO</td></tr><tr><td>BasicAuthenticationFilter</td><td>处理HttpBasic登录</td><td>YES</td></tr><tr><td>RequestCacheAwareFilter</td><td>处理请求缓存</td><td>YES</td></tr><tr><td>SecurityContextHolderAwareRequestFilter</td><td>包装原始请求</td><td>YES</td></tr><tr><td>JaasApiIntegrationFilter</td><td>处理JAAS认证</td><td>NO</td></tr><tr><td>RememberMeAuthenticationFilter</td><td>处理RememberMe登录</td><td>NO</td></tr><tr><td>AnonymousAuthenticationFilter</td><td>配置匿名认证</td><td>YES</td></tr><tr><td>OAuth2AuthorizationCodeGrantFilter</td><td>处理OAuth2认证中的授权码</td><td>NO</td></tr><tr><td>SessionManagementFilter</td><td>处理Session并发问题</td><td>YES</td></tr><tr><td>ExceptionTranslationFilter</td><td>池异常认证&#x2F;授权中的情况</td><td>YES</td></tr><tr><td>FilterSecurityInterceptor</td><td>处理授权</td><td>YES</td></tr><tr><td>SwitchUserFilter</td><td>处理账户切换</td><td>NO</td></tr></tbody></table><p>Spring Security提供的功能基本都是由上述过滤器来实现的，这些过滤器按照既定的优先级排列形成过滤器链。当然开发者也可以自定义过滤器的位置，这里暂不讨论。</p><h2 id="Spring-Security登录认证"><a href="#Spring-Security登录认证" class="headerlink" title="Spring Security登录认证"></a>Spring Security登录认证</h2><p>上图中新建了一个最基本的Spring Security demo ，在访问&#x2F;hello的时候，会自动重定向到&#x2F;login页面，并且需要输入账号密码登录</p><p>而在启动时，发现终端上打印了一行</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Using generated security password: 535d2c4d-44eb-4808-8ae1-f272b9317b29</span><br></pre></td></tr></table></figure><p>也就是Spring Security 随机生成的密码（uuid） 默认用户名为user。</p><h3 id="流程分析"><a href="#流程分析" class="headerlink" title="流程分析"></a>流程分析</h3><p>上述流程以图标的方式展现如下</p><p><img src="https://pic.imgdb.cn/item/64c8c3661ddac507ccdcbe59.png"></p><ol><li>客户端（浏览器）发起请求  <code>/hello</code> 接口，由于引入了<code>Spring Security</code>，登录需要认证</li><li>请求会走一遍<code>Spring Security</code> 中的过滤链，在最后的<code>FilterSecurityInterceptor</code>过滤其中被拦截下来，因为系统发现用户未认证。请求拦截下拉后会抛出 <code>AccessDeniedException</code> 异常</li><li>抛出的<code>AccessDeniedException</code> 异常 在 <code>ExceptionTranslationFilter</code>过滤器中被捕获，<code>ExceptionTranslationFilter</code> 过滤器通过调用 <code>LoginUrlAuthenticationEntryPoint</code>中 <code>commmence</code>方法返回客户端302，要求客户端重定向到&#x2F;login页面</li><li>客户端重新发送 &#x2F;login请求</li><li>&#x2F;login 请求被 <code>DefaultLoginPageGeneratingFilter</code>过滤器拦截下来，并在该过滤器中返回登录页面。</li></ol><h3 id="代码分析"><a href="#代码分析" class="headerlink" title="代码分析"></a>代码分析</h3><h4 id="FilterSecurityInterceptor"><a href="#FilterSecurityInterceptor" class="headerlink" title="FilterSecurityInterceptor"></a>FilterSecurityInterceptor</h4><p>当过滤链执行到<code>FilterSecurityInterceptor</code>时，会调用<code>doFilter</code>方法如下，可以看到主要调用了<code>invoke</code>方法，<code>FilterInvocation</code>分装了当前请求信息。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">doFilter</span><span class="params">(ServletRequest request, ServletResponse response, FilterChain chain)</span> <span class="keyword">throws</span> IOException, ServletException &#123;</span><br><span class="line">    <span class="type">FilterInvocation</span> <span class="variable">fi</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">FilterInvocation</span>(request, response, chain);</span><br><span class="line">    <span class="built_in">this</span>.invoke(fi);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在<code>invoke()</code>方法中，它会调用<code>beforeInvocation()</code>方法，该方法是<code>AbstractSecurityInterceptor</code>的一个抽象方法，具体的实现由子类提供。<code>beforeInvocation()</code>方法会调用<code>AccessDecisionManager</code>来进行访问决策。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">invoke</span><span class="params">(FilterInvocation fi)</span> <span class="keyword">throws</span> IOException, ServletException &#123;</span><br><span class="line">    <span class="keyword">if</span> (fi.getRequest() != <span class="literal">null</span> &amp;&amp; fi.getRequest().getAttribute(<span class="string">&quot;__spring_security_filterSecurityInterceptor_filterApplied&quot;</span>) != <span class="literal">null</span> &amp;&amp; <span class="built_in">this</span>.observeOncePerRequest) &#123;</span><br><span class="line">        fi.getChain().doFilter(fi.getRequest(), fi.getResponse());</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (fi.getRequest() != <span class="literal">null</span> &amp;&amp; <span class="built_in">this</span>.observeOncePerRequest) &#123;</span><br><span class="line">            fi.getRequest().setAttribute(<span class="string">&quot;__spring_security_filterSecurityInterceptor_filterApplied&quot;</span>, Boolean.TRUE);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="type">InterceptorStatusToken</span> <span class="variable">token</span> <span class="operator">=</span> <span class="built_in">super</span>.beforeInvocation(fi);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            fi.getChain().doFilter(fi.getRequest(), fi.getResponse());</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            <span class="built_in">super</span>.finallyInvocation(token);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="built_in">super</span>.afterInvocation(token, (Object)<span class="literal">null</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在<code>beforeInvocation</code>方法中并不会直接抛出异常。相反，它会委托给<code>AccessDecisionManager</code>来进行访问决策。如果访问决策失败，<code>AccessDecisionManager</code>将抛出<code>AccessDeniedException</code>异常，表示访问被拒绝。主要语句是</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="built_in">this</span>.accessDecisionManager.decide(authenticated, object, attributes);</span><br><span class="line">&#125; <span class="keyword">catch</span> (AccessDeniedException var7) &#123;</span><br><span class="line">    <span class="built_in">this</span>.publishEvent(<span class="keyword">new</span> <span class="title class_">AuthorizationFailureEvent</span>(object, attributes, authenticated, var7));</span><br><span class="line">    <span class="keyword">throw</span> var7;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里，<code>FilterSecurityInterceptor</code>会检查用户的认证状态和权限，然后调用<code>AccessDecisionManager</code>的<code>decide</code>方法来进行访问决策。</p><p>这里默认的决策器是<code>AffirmativeBased</code>，相关代码为</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">while</span>(var5.hasNext()) &#123;</span><br><span class="line">    <span class="type">AccessDecisionVoter</span> <span class="variable">voter</span> <span class="operator">=</span> (AccessDecisionVoter)var5.next();</span><br><span class="line">    <span class="type">int</span> <span class="variable">result</span> <span class="operator">=</span> voter.vote(authentication, object, configAttributes);</span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">this</span>.logger.isDebugEnabled()) &#123;</span><br><span class="line">        <span class="built_in">this</span>.logger.debug(<span class="string">&quot;Voter: &quot;</span> + voter + <span class="string">&quot;, returned: &quot;</span> + result);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">switch</span>(result) &#123;</span><br><span class="line">    <span class="keyword">case</span> -<span class="number">1</span>:</span><br><span class="line">        ++deny;</span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">    <span class="keyword">case</span> <span class="number">1</span>:</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (deny &gt; <span class="number">0</span>) &#123;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">AccessDeniedException</span>(<span class="built_in">this</span>.messages.getMessage(<span class="string">&quot;AbstractAccessDecisionManager.accessDenied&quot;</span>, <span class="string">&quot;Access is denied&quot;</span>));</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="built_in">this</span>.checkAllowIfAllAbstainDecisions();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>AffirmativeBased</code>: 如果任何一个访问决策器返回<code>ACCESS_GRANTED</code>（访问允许），则决策通过；否则，如果所有访问决策器都返回<code>ACCESS_ABSTAIN</code>（弃权）或没有决策器，那么访问也会被允许。</p><p><code>AffirmativeBased</code> 默认传入的构造器只有一个 <code>WebExpressionVoter</code>，这个构造器会根据你在配置文件中的配置进行逻辑处理得出投票结果，这里返回 -1，deny &gt; 0 直接抛出异常。</p><h4 id="ExceptionTranslationFilter"><a href="#ExceptionTranslationFilter" class="headerlink" title="ExceptionTranslationFilter"></a>ExceptionTranslationFilter</h4><p><code>ExceptionTranslationFilter</code> 捕获<code>FilterSecurityInterceptor</code>抛出的<code>AccessDeniedException</code>异常，通过调用 <code>LoginUrlAuthenticationEntryPoint</code>中 <code>commmence</code>方法返回客户端302，要求客户端重定向到&#x2F;login页面。</p><p>这里走的是<code>catch(Excception var10)</code></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">doFilter</span><span class="params">(ServletRequest req, ServletResponse res, FilterChain chain)</span> <span class="keyword">throws</span> IOException, ServletException &#123;</span><br><span class="line">    <span class="type">HttpServletRequest</span> <span class="variable">request</span> <span class="operator">=</span> (HttpServletRequest)req;</span><br><span class="line">    <span class="type">HttpServletResponse</span> <span class="variable">response</span> <span class="operator">=</span> (HttpServletResponse)res;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        chain.doFilter(request, response);</span><br><span class="line">        <span class="built_in">this</span>.logger.debug(<span class="string">&quot;Chain processed normally&quot;</span>);</span><br><span class="line">    &#125; <span class="keyword">catch</span> (IOException var9) &#123;</span><br><span class="line">        <span class="keyword">throw</span> var9;</span><br><span class="line">    &#125; <span class="keyword">catch</span> (Exception var10) &#123;</span><br><span class="line">    ...........</span><br><span class="line"></span><br><span class="line">        <span class="built_in">this</span>.handleSpringSecurityException(request, response, chain, (RuntimeException)ase);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>handleSpringSecurityException</code>方法主要是禁止访问和重定向</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">handleSpringSecurityException</span><span class="params">(HttpServletRequest request, HttpServletResponse response, FilterChain chain, RuntimeException exception)</span> <span class="keyword">throws</span> IOException, ServletException &#123;</span><br><span class="line">    <span class="keyword">if</span> (exception <span class="keyword">instanceof</span> AuthenticationException) &#123;</span><br><span class="line">        <span class="built_in">this</span>.logger.debug(<span class="string">&quot;Authentication exception occurred; redirecting to authentication entry point&quot;</span>, exception);</span><br><span class="line">        <span class="built_in">this</span>.sendStartAuthentication(request, response, chain, (AuthenticationException)exception);</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (exception <span class="keyword">instanceof</span> AccessDeniedException) &#123;</span><br><span class="line">        <span class="type">Authentication</span> <span class="variable">authentication</span> <span class="operator">=</span> SecurityContextHolder.getContext().getAuthentication();</span><br><span class="line">        <span class="keyword">if</span> (!<span class="built_in">this</span>.authenticationTrustResolver.isAnonymous(authentication) &amp;&amp; !<span class="built_in">this</span>.authenticationTrustResolver.isRememberMe(authentication)) &#123;</span><br><span class="line">            <span class="built_in">this</span>.logger.debug(<span class="string">&quot;Access is denied (user is not anonymous); delegating to AccessDeniedHandler&quot;</span>, exception);</span><br><span class="line">            <span class="built_in">this</span>.accessDeniedHandler.handle(request, response, (AccessDeniedException)exception);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="built_in">this</span>.logger.debug(<span class="string">&quot;Access is denied (user is &quot;</span> + (<span class="built_in">this</span>.authenticationTrustResolver.isAnonymous(authentication) ? <span class="string">&quot;anonymous&quot;</span> : <span class="string">&quot;not fully authenticated&quot;</span>) + <span class="string">&quot;); redirecting to authentication entry point&quot;</span>, exception);</span><br><span class="line">            <span class="built_in">this</span>.sendStartAuthentication(request, response, chain, <span class="keyword">new</span> <span class="title class_">InsufficientAuthenticationException</span>(<span class="built_in">this</span>.messages.getMessage(<span class="string">&quot;ExceptionTranslationFilter.insufficientAuthentication&quot;</span>, <span class="string">&quot;Full authentication is required to access this resource&quot;</span>)));</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>显而易见这里走的是 <code>this.sendStartAuthentication(request, response, chain, new InsufficientAuthenticationException(this.messages.getMessage(&quot;ExceptionTranslationFilter.insufficientAuthentication&quot;, &quot;Full authentication is required to access this resource&quot;)));</code></p><p>而<code>sendStartAuthentication</code>实际上调用的就是<code>LoginUrlAuthenticationEntryPoint</code>的<code>commence</code>方法</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">sendStartAuthentication</span><span class="params">(HttpServletRequest request, HttpServletResponse response, FilterChain chain, AuthenticationException reason)</span> <span class="keyword">throws</span> ServletException, IOException &#123;</span><br><span class="line">    SecurityContextHolder.getContext().setAuthentication((Authentication)<span class="literal">null</span>);</span><br><span class="line">    <span class="built_in">this</span>.requestCache.saveRequest(request, response);</span><br><span class="line">    <span class="built_in">this</span>.logger.debug(<span class="string">&quot;Calling Authentication entry point.&quot;</span>);</span><br><span class="line">    <span class="built_in">this</span>.authenticationEntryPoint.commence(request, response, reason);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>commence</code>真正实现了由<code>Spring Security</code> 提供的默认<code>/login</code> 路径的重定向</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">commence</span><span class="params">(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)</span> <span class="keyword">throws</span> IOException, ServletException &#123;</span><br><span class="line">    <span class="type">String</span> <span class="variable">redirectUrl</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">this</span>.useForward) &#123;</span><br><span class="line">        <span class="keyword">if</span> (<span class="built_in">this</span>.forceHttps &amp;&amp; <span class="string">&quot;http&quot;</span>.equals(request.getScheme())) &#123;</span><br><span class="line">            redirectUrl = <span class="built_in">this</span>.buildHttpsRedirectUrlForRequest(request);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (redirectUrl == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="type">String</span> <span class="variable">loginForm</span> <span class="operator">=</span> <span class="built_in">this</span>.determineUrlToUseForThisRequest(request, response, authException);</span><br><span class="line">            <span class="keyword">if</span> (logger.isDebugEnabled()) &#123;</span><br><span class="line">                logger.debug(<span class="string">&quot;Server side forward to: &quot;</span> + loginForm);</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="type">RequestDispatcher</span> <span class="variable">dispatcher</span> <span class="operator">=</span> request.getRequestDispatcher(loginForm);</span><br><span class="line">            dispatcher.forward(request, response);</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        redirectUrl = <span class="built_in">this</span>.buildRedirectUrlToLoginPage(request, response, authException);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">this</span>.redirectStrategy.sendRedirect(request, response, redirectUrl);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="DefaultLoginPageGeneratingFilter"><a href="#DefaultLoginPageGeneratingFilter" class="headerlink" title="DefaultLoginPageGeneratingFilter"></a>DefaultLoginPageGeneratingFilter</h4><p>经过上述重定向操作，客户端重新发起请求&#x2F;hello，这个请求会在<code>DefaultLoginPageGeneratingFilter</code>被拦截下拉，并返回默认的登录页面</p><p><code>DefaultLoginPageGeneratingFilter</code>的操作要简单些，当匹配到登录操作的时候（else)，会发送默认生成的页面，也就是我们看到的demo 登录页。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">doFilter</span><span class="params">(ServletRequest req, ServletResponse res, FilterChain chain)</span> <span class="keyword">throws</span> IOException, ServletException &#123;</span><br><span class="line">    <span class="type">HttpServletRequest</span> <span class="variable">request</span> <span class="operator">=</span> (HttpServletRequest)req;</span><br><span class="line">    <span class="type">HttpServletResponse</span> <span class="variable">response</span> <span class="operator">=</span> (HttpServletResponse)res;</span><br><span class="line">    <span class="type">boolean</span> <span class="variable">loginError</span> <span class="operator">=</span> <span class="built_in">this</span>.isErrorPage(request);</span><br><span class="line">    <span class="type">boolean</span> <span class="variable">logoutSuccess</span> <span class="operator">=</span> <span class="built_in">this</span>.isLogoutSuccess(request);</span><br><span class="line">    <span class="keyword">if</span> (!<span class="built_in">this</span>.isLoginUrlRequest(request) &amp;&amp; !loginError &amp;&amp; !logoutSuccess) &#123;</span><br><span class="line">        chain.doFilter(request, response);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">// 顾名思义，生成默认页面，详细代码可以点开generateLoginPageHtml方法</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">loginPageHtml</span> <span class="operator">=</span> <span class="built_in">this</span>.generateLoginPageHtml(request, loginError, logoutSuccess);</span><br><span class="line">        response.setContentType(<span class="string">&quot;text/html;charset=UTF-8&quot;</span>);</span><br><span class="line">        response.setContentLength(loginPageHtml.getBytes(StandardCharsets.UTF_8).length);</span><br><span class="line">        response.getWriter().write(loginPageHtml);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>至此，解释了从输入<code>/hello</code> 到 重定向到 <code>/login</code>代码的原理，由于它的实现过程非常复杂，这里也只是定位到本次请求经过的代码链路操作。</p><p>除此之外，还有一个问题，就是生成了默认的登录页，那在哪里生成的默认账号和密码呢？后续将继续介绍</p><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><p>《深入浅出Spring Security》王松</p>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;\assets\css\APlayer.min.css&quot;&gt;&lt;script src=&quot;\assets\js\APlayer.min.js&quot; cla</summary>
      
    
    
    
    <category term="SpringSecurity" scheme="https://nerkeler.github.io/categories/SpringSecurity/"/>
    
    
    <category term="SpringSecurity" scheme="https://nerkeler.github.io/tags/SpringSecurity/"/>
    
    <category term="spring" scheme="https://nerkeler.github.io/tags/spring/"/>
    
  </entry>
  
  <entry>
    <title>MySQL知识网络(一)-MYSQL结构体系</title>
    <link href="https://nerkeler.github.io/2023/07/21/MySQL%E7%9F%A5%E8%AF%86%E7%BD%91%E7%BB%9C-%E4%B8%80-MYSQL%E7%BB%93%E6%9E%84%E4%BD%93%E7%B3%BB/"/>
    <id>https://nerkeler.github.io/2023/07/21/MySQL%E7%9F%A5%E8%AF%86%E7%BD%91%E7%BB%9C-%E4%B8%80-MYSQL%E7%BB%93%E6%9E%84%E4%BD%93%E7%B3%BB/</id>
    <published>2023-07-21T06:20:00.000Z</published>
    <updated>2025-05-25T07:21:11.407Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h1 id="MySQL知识网络（一）"><a href="#MySQL知识网络（一）" class="headerlink" title="MySQL知识网络（一）"></a>MySQL知识网络（一）</h1><h3 id="一条SQL是怎么执行的"><a href="#一条SQL是怎么执行的" class="headerlink" title="一条SQL是怎么执行的"></a>一条SQL是怎么执行的</h3><p>入门java时观看宋红康老师的视频中有句话让我印象深刻，大处着眼，小处着手。学习一个新的技术，应该先<em><strong>鸟瞰其全貌，从高维度理解问题</strong></em>。在我们输入一个简单的SQL查询语句时，心中应该对他的整体执行流程有个概念，做到胸有成竹，才能在遇到问题时，准确定位，进而根据情况对应解决方案。而想要做到这点，不得不提的就是MYSQL结构体系。</p><h3 id="MYSQL结构体系-Server层"><a href="#MYSQL结构体系-Server层" class="headerlink" title="MYSQL结构体系(Server层)"></a>MYSQL结构体系(Server层)</h3><p><img src="https://pic.imgdb.cn/item/64b8cab61ddac507ccf5b8e4.png"></p><p>大体来说，MySQL可以分为Server层和存储引擎层两部分</p><p>Server层包括连接器、查询缓存、分析器、优化器、执行器等，涵盖MySQL的大多数核心服务功能，以及所有的内置函数（如日期、时间、数学和加密函数等），所有跨存储引擎的功能都在这一层实现，比如存存过程、触发器、视图等。</p><p>存储引擎层负责数据的存储和提取，其架构模式是插件式的，支持InnoDB、MyISAM、Memory等多个存储引擎。现在最常用的存储引擎是InnoDB,它从MySQL 5.5.5版本开始成为了默认存储引擎。</p><h4 id="连接器和安全管理"><a href="#连接器和安全管理" class="headerlink" title="连接器和安全管理"></a>连接器和安全管理</h4><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mysql <span class="operator">-</span>h$ip <span class="operator">-</span>P$port <span class="operator">-</span>u$<span class="keyword">user</span> <span class="operator">-</span>p</span><br></pre></td></tr></table></figure><p>使用上述命令登录mysql服务器，其中</p><ul><li>h （host）代表host 也就是主机ip</li><li>p (port)代表端口</li><li>u (user)代表用户名</li><li>p (password)代表密码，-p后面没填写密码是为了在终端中不可见的输入密码，一定程度上避免密码泄露。</li></ul><p>我们一般使用的可视化工具需要提供的也是上述参数，如navicat</p><p><img src="https://pic.imgdb.cn/item/64b9056f1ddac507ccd40880.png"></p><p>除了常规的账号密码登录，也可以使用套接字（SSL）登录，一旦客户端链接成功，服务器会继续验证客户端是否具有执行某个特定查询的权限。</p><h4 id="查询缓存"><a href="#查询缓存" class="headerlink" title="查询缓存"></a>查询缓存</h4><p>在建立连接后，则进入了Server层执行查询条件，而查询首先要做的就是查询缓存</p><p>如上图中所示，MySQL在拿到一个查询请求后首先回到查询缓存中看看之前是否执行过这条语句。之前执行的语句及结果会以key-value形式存储在内存中，如果在缓存中找到这个查询语句的key，则直接返回value。反之则会继续后续的查询流程。</p><p>直接从缓存中查询会大大提高查询效率，但是实际使用中并不推荐这么做，因为在原SQL涉及表中，如果任一表格中的数据被更新（包括新建和删除），缓存则将失效，对于更新频繁的表格，缓存命中的几率会很小。缓存的使用则适合表更新不频繁情景下。</p><p><em><strong>MySQL 8.0版本直接将查询缓存的整块功能删掉了，也就是说8.0开始彻底没有缓存功能了。</strong></em></p><h4 id="分析器"><a href="#分析器" class="headerlink" title="分析器"></a>分析器</h4><p>如果没有命中查询缓存，则进入分析器阶段进行此法分析和语法分析</p><p>所谓词法分析就是将我们输入的一整条SQL语句，拆解成对应的表名和字段等，语法分析则是根据MySQL语法规则判断输入的SQL语句是否符合语法。</p><p>举个例子，当我们输入以下SQL语句，则会收到<em><strong>“You have an error in your SQL syntax”</strong></em>的错误提醒。原因则是select少打了一个t</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">selec <span class="operator">*</span> <span class="keyword">from</span> t <span class="keyword">where</span> ID<span class="operator">=</span><span class="number">1</span>; </span><br><span class="line"></span><br><span class="line"><span class="comment">-- 1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near &#x27;SELEC * FROM `T`&#x27; at line 1</span></span><br></pre></td></tr></table></figure><h4 id="优化器"><a href="#优化器" class="headerlink" title="优化器"></a>优化器</h4><p>经过了分析器，MySQL就知道你要做什么了。在开始执行之前，还要先经过优化器的处理。</p><p>优化器是在表中有多个索引的时候决定使用哪个索引，或者在一个语句关联多张表的时候，决定各个表的链接顺序</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> t1 <span class="keyword">join</span> t2 <span class="keyword">using</span>(ID)  <span class="keyword">where</span> t1.c<span class="operator">=</span><span class="number">10</span> <span class="keyword">and</span> t2.d<span class="operator">=</span><span class="number">20</span>;</span><br></pre></td></tr></table></figure><ul><li>即可以先从表t1里面取出 c&#x3D;10的记录的ID值，再根据ID值关联到表t2,再判断t2里面的d的值是否等于20</li><li>也可以先从t2里面取出d &#x3D; 20的ID值，再根据ID关联T1，再判断t1的c值是否等于10</li></ul><p>这两种执行方法的逻辑结果是一样的，但是执行的效率会有不同，而优化器的作用就是决定选择使用哪一个方案。</p><h4 id="执行器"><a href="#执行器" class="headerlink" title="执行器"></a>执行器</h4><p>分析器告诉我们改做什么，优化器告诉我们改怎么做，那么执行器就是要具体去做了</p><p>不过再执行之前测绘判断一下当前用户对SQL语句涉及到的表是否有执行查询的权限，如果没有，那边就会返回没有权限的错误。</p><p>如果有权限，则打开表执行查询语句，执行器通过表的引擎定义，去使用这个引擎提供的接口（如InnoDB)</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">show</span> <span class="keyword">table</span> STATUS</span><br><span class="line"></span><br><span class="line"><span class="comment">-- ordersInnoDB10Dynamic2081916384000212023-07-21 02:09:312023-07-21 02:10:01utf8mb4_0900_ai_ci</span></span><br></pre></td></tr></table></figure><p>使用上述命令会展示当前表的信息, 其中InnoDB 表示这张表使用的引擎是InnoDB.</p><p>具体查询表格的细节后续会再详细描述。</p><h3 id="MySQL的存储引擎"><a href="#MySQL的存储引擎" class="headerlink" title="MySQL的存储引擎"></a>MySQL的存储引擎</h3><p>MySQL的存储引擎有很多，本文只简略介绍几个常见的，并且也不会详细介绍，只是为了做到有个印象。</p><h4 id="InnoDB存储引擎"><a href="#InnoDB存储引擎" class="headerlink" title="InnoDB存储引擎"></a>InnoDB存储引擎</h4><p>InnoDB是MySQL的默认事务型存储引擎，采用MVCC支持高并发，并实现了四个标准的隔离级别。其默认的隔离级别是REPEATABLE READ（可重复读），并且通过间隙锁（next-key locking)策略防止幻读的出现，间隙锁使得InnoDB不仅仅锁定查询涉及的行，还会对索引中的间隙进行锁定，以防止幻影行的插入。</p><h4 id="MyISAM存储引擎"><a href="#MyISAM存储引擎" class="headerlink" title="MyISAM存储引擎"></a>MyISAM存储引擎</h4><p>再MySQL5.1及之前的版本中，MyISAM是默认的存储引擎。MyISAM提供了大量的特性，包括全文索引、压缩、控件函数（GIS)等，但MyISAM不支持事务和行级锁，而且再崩溃后无法安全恢复。</p><p>现在MySQL默认引擎已经是InnoDB，所以MyISAM就不过多讨论了，需要知道的是MyISAM不支持事务，并且只支持表锁。</p><h4 id="Memory引擎"><a href="#Memory引擎" class="headerlink" title="Memory引擎"></a>Memory引擎</h4><p>类似于Redis,Memory引擎把所有的数据都保存再内存中，因而查询速度很快，因为存储再内存中，所以数据再机器重启后会丢失，但是表结构会保留。</p><p>当执行MySQL查询时需要使用临时表保存中间结果，这时候使用的临时表就是Memory表，不过当结果太大超出Memory表限制或者含有BLOB或者TEXT字段，临时表会转换位MyISAM表。</p><h3 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h3><p>通过一条SQL的执行流程，简略介绍了MySQL中的内部结构组成，包括 连接器、分析器、优化器、执行器、和MySQL的存储引擎，文章主要还是简略介绍，只是为了有个整体的概念，相关细节待后续补充</p><h3 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h3><p><a href="https://funnylog.gitee.io/mysql45/">林晓斌-极客时间MySQL45讲</a></p><p>《高性能MySQL》</p><p>《MySQL技术内幕-InnoDB存储引擎》</p>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;\assets\css\APlayer.min.css&quot;&gt;&lt;script src=&quot;\assets\js\APlayer.min.js&quot; cla</summary>
      
    
    
    
    <category term="MySQL" scheme="https://nerkeler.github.io/categories/MySQL/"/>
    
    
    <category term="MySQL" scheme="https://nerkeler.github.io/tags/MySQL/"/>
    
  </entry>
  
  <entry>
    <title>JWT详解</title>
    <link href="https://nerkeler.github.io/2023/06/27/JWT%E8%AF%A6%E8%A7%A3/"/>
    <id>https://nerkeler.github.io/2023/06/27/JWT%E8%AF%A6%E8%A7%A3/</id>
    <published>2023-06-27T05:55:00.000Z</published>
    <updated>2024-01-04T01:47:47.890Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h1 id="JWT详解"><a href="#JWT详解" class="headerlink" title="JWT详解"></a>JWT详解</h1><p><strong>转载自：<a href="https://baobao555.tech/archives/40">JWT详解 (baobao555.tech)</a></strong>*</p><h2 id="JWT简介"><a href="#JWT简介" class="headerlink" title="JWT简介"></a>JWT简介</h2><h2 id="1-什么是JWT"><a href="#1-什么是JWT" class="headerlink" title="1.什么是JWT"></a>1.什么是JWT</h2><p>在介绍JWT之前，我们先来回顾一下利用<code>token</code>进行用户身份验证的流程：</p><ol><li>客户端使用用户名和密码请求登录</li><li>服务端收到请求，验证用户名和密码</li><li>验证成功后，服务端会签发一个<code>token</code>，再把这个<code>token</code>返回给客户端</li><li>客户端收到token后可以把它存储起来，比如放到cookie中</li><li>客户端每次向服务端请求资源时需要携带服务端签发的token，可以在<code>cookie</code>或者<code>header</code>中携带</li><li>服务端收到请求，然后去验证客户端请求里面带着的<code>token</code>，如果验证成功，就向客户端返回请求数据</li></ol><p> 这种基于<code>token</code>的认证方式相比传统的<code>session</code>认证方式更节约服务器资源，并且对移动端和分布式更加友好。其优点如下：</p><ul><li><code>支持跨域访问</code>：<code>cookie</code>是无法跨域的，而<code>token</code>由于没有用到<code>cookie</code>(前提是将<code>token</code>放到请求头中)，所以跨域后不会存在信息丢失问题</li><li><code>无状态</code>：<code>token</code>机制在服务端不需要存储<code>session</code>信息，因为token自身包含了所有登录用户的信息，所以可以减轻服务端压力</li><li><code>更适用CDN</code>：可以通过内容分发网络请求服务端的所有资料</li><li><code>更适用于移动端</code>：当客户端是非浏览器平台时，<code>cookie</code>是不被支持的，此时采用<code>token</code>认证方式会简单很多</li><li><code>无需考虑CSRF</code>：由于不再依赖<code>cookie</code>，所以采用token认证方式不会发生CSRF，所以也就无需考虑CSRF的防御</li></ul><p> 而<code>JWT</code>就是上述流程当中<code>token</code>的一种具体实现方式，其全称是<code>JSON Web Token</code>，官网地址：<a href="https://jwt.io/">https://jwt.io/</a></p><p> 通俗地说，<code>JWT的本质就是一个字符串</code>，它是将用户信息保存到一个Json字符串中，然后进行编码后得到一个<code>JWT token</code>，<code>并且这个JWT token带有签名信息，接收后可以校验是否被篡改</code>，所以可以用于在各方之间安全地将信息作为Json对象传输。JWT的认证流程如下：</p><ol><li>首先，前端通过Web表单将自己的用户名和密码发送到后端的接口，这个过程一般是一个<code>POST</code>请求。建议的方式是通过SSL加密的传输(HTTPS)，从而避免敏感信息被嗅探</li><li>后端核对用户名和密码成功后，<code>将包含用户信息的数据作为JWT的Payload，将其与JWT Header分别进行Base64编码拼接后签名</code>，形成一个<code>JWT Token</code>，形成的<code>JWT Token</code>就是一个如同<code>lll.zzz.xxx</code>的字符串</li><li>后端将<code>JWT Token</code>字符串作为登录成功的结果返回给前端。前端可以将返回的结果保存在浏览器中，退出登录时删除保存的<code>JWT Token</code>即可</li><li><code>前端在每次请求时将JWT Token放入HTTP请求头中的Authorization属性中</code>(解决XSS和XSRF问题)</li><li>后端检查前端传过来的<code>JWT Token</code>，验证其有效性，比如检查签名是否正确、是否过期、token的接收方是否是自己等等</li><li>验证通过后，后端解析出<code>JWT Token</code>中包含的用户信息，进行其他逻辑操作(一般是根据用户信息得到权限等)，返回结果</li></ol><p> <img src="https://pic.imgdb.cn/item/649a7ae81ddac507cca6ac94.png"></p><h2 id="2-为什么要用JWT"><a href="#2-为什么要用JWT" class="headerlink" title="2.为什么要用JWT"></a>2.为什么要用JWT</h2><h3 id="2-1-传统Session认证的弊端"><a href="#2-1-传统Session认证的弊端" class="headerlink" title="2.1 传统Session认证的弊端"></a>2.1 传统Session认证的弊端</h3><p> 我们知道<code>HTTP本身是一种无状态的协议</code>，这就意味着如果用户向我们的应用提供了用户名和密码来进行用户认证，认证通过后HTTP协议不会记录下认证后的状态，那么下一次请求时，用户还要再一次进行认证，因为根据HTTP协议，我们并不知道是哪个用户发出的请求，所以为了让我们的应用能识别是哪个用户发出的请求，我们只能在用户首次登录成功后，在服务器存储一份用户登录的信息，这份登录信息会在响应时传递给浏览器，告诉其保存为<code>cookie</code>，以便下次请求时发送给我们的应用，这样我们的应用就能识别请求来自哪个用户了，这是传统的基于<code>session</code>认证的过程</p><p> <img src="https://pic.imgdb.cn/item/649a7b711ddac507cca7e1c3.png"></p><p> 然而，传统的<code>session</code>认证有如下的问题：</p><ul><li>每个用户的登录信息都会保存到服务器的<code>session</code>中，<code>随着用户的增多，服务器开销会明显增大</code></li><li>由于<code>session</code>是存在与服务器的物理内存中，所以<code>在分布式系统中，这种方式将会失效</code>。虽然可以将<code>session</code>统一保存到Redis中，但是这样做无疑增加了系统的复杂性，对于不需要redis的应用也会白白多引入一个缓存中间件</li><li><code>对于非浏览器的客户端、手机移动端等不适用</code>，因为<code>session</code>依赖于<code>cookie</code>，而移动端经常没有<code>cookie</code></li><li>因为<code>session</code>认证本质基于<code>cookie</code>，所以如果<code>cookie</code>被截获，用户很容易收到跨站请求伪造攻击。并且如果浏览器禁用了<code>cookie</code>，这种方式也会失效</li><li>前后端分离系统中更加不适用，后端部署复杂，前端发送的请求往往经过多个中间件到达后端，<code>cookie</code>中关于<code>session</code>的信息会转发多次</li><li>由于基于Cookie，而<code>cookie无法跨域，所以session的认证也无法跨域，对单点登录不适用</code></li></ul><h3 id="2-2-JWT认证的优势"><a href="#2-2-JWT认证的优势" class="headerlink" title="2.2 JWT认证的优势"></a>2.2 JWT认证的优势</h3><p> 对比传统的<code>session</code>认证方式，JWT的优势是：</p><ul><li>简洁：<code>JWT Token</code>数据量小，传输速度也很快</li><li>因为JWT Token是以JSON加密形式保存在客户端的，所以JWT是<code>跨语言</code>的，原则上任何web形式都支持</li><li>不需要在服务端保存会话信息，也就是说<code>不依赖于cookie和session，所以没有了传统session认证的弊端，特别适用于分布式微服务</code></li><li><code>单点登录友好</code>：使用Session进行身份认证的话，由于cookie无法跨域，难以实现单点登录。但是，使用token进行认证的话， <code>token可以被保存在客户端的任意位置的内存中，不一定是cookie，所以不依赖cookie</code>，不会存在这些问题</li><li><code>适合移动端应用</code>：使用Session进行身份认证的话，需要保存一份信息在服务器端，而且这种方式会依赖到Cookie（需要 Cookie 保存 SessionId），所以不适合移动端</li></ul><p>  因为这些优势，目前无论单体应用还是分布式应用，都更加<code>推荐用JWT token的方式进行用户认证</code></p><h2 id="JWT结构"><a href="#JWT结构" class="headerlink" title="JWT结构"></a>JWT结构</h2><p> JWT由3部分组成：标头(Header)、有效载荷(Payload)和签名(Signature)。在传输的时候，会将JWT的3部分分别进行Base64编码后用<code>.</code>进行连接形成最终传输的字符串</p><p> <code>JWTString\=Base64(Header).Base64(Payload).HMACSHA256(base64UrlEncode(header)+&quot;.&quot;+base64UrlEncode(payload),secret)</code></p><p> <code>JWTString = Base64(Header).Base64(Payload).HMACSHA256(base64UrlEncode(header) + &quot;.&quot; + base64UrlEncode(payload), secret)</code></p><p> <img src="https://pic.imgdb.cn/item/649a7bd41ddac507cca8a236.png"></p><h3 id="1-Header"><a href="#1-Header" class="headerlink" title="1.Header"></a>1.Header</h3><p> <code>JWT头</code>是一个描述JWT元数据的JSON对象，alg属性表示签名使用的算法，默认为HMAC SHA256（写为HS256）；typ属性表示令牌的类型，JWT令牌统一写为JWT。最后，使用Base64 URL算法将上述JSON对象转换为字符串保存</p> <figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;alg&quot;</span><span class="punctuation">:</span> <span class="string">&quot;HS256&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;typ&quot;</span><span class="punctuation">:</span> <span class="string">&quot;JWT&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h3 id="2-Payload"><a href="#2-Payload" class="headerlink" title="2.Payload"></a>2.Payload</h3><p> <code>有效载荷</code>部分，是JWT的主体内容部分，也是一个<code>JSON对象</code>，包含需要传递的数据。 JWT指定七个默认字段供选择</p> <figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">iss：发行人</span><br><span class="line">exp：到期时间</span><br><span class="line">sub：主题</span><br><span class="line">aud：用户</span><br><span class="line">nbf：在此之前不可用</span><br><span class="line">iat：发布时间</span><br><span class="line">jti：JWT ID用于标识该JWT</span><br></pre></td></tr></table></figure><p> 除以上默认字段外，我们还可以自定义私有字段，<code>一般会把包含用户信息的数据放到payload中</code>，如下例：</p> <figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;sub&quot;</span><span class="punctuation">:</span> <span class="string">&quot;1234567890&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Helen&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;admin&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>  请注意，<code>默认情况下JWT是未加密的，因为只是采用base64算法，拿到JWT字符串后可以转换回原本的JSON数据，任何人都可以解读其内容，因此不要构建隐私信息字段，比如用户的密码一定不能保存到JWT中</code>，以防止信息泄露。<code>JWT只是适合在网络中传输一些非敏感的信息</code></p><h3 id="3-Signature"><a href="#3-Signature" class="headerlink" title="3.Signature"></a>3.Signature</h3><p> <code>签名哈希</code>部分是对上面两部分数据签名，需要使用base64编码后的header和payload数据，通过指定的算法生成哈希，以<code>确保数据不会被篡改</code>。首先，需要指定一个密钥（secret）。该密码仅仅为保存在服务器中，并且不能向用户公开。然后，使用header中指定的签名算法（默认情况下为HMAC SHA256）根据以下公式生成签名</p> <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">HMACSHA256(base64UrlEncode(header)+<span class="string">&quot;.&quot;</span>+base64UrlEncode(payload),secret)HMACSHA256(base64UrlEncode(header) + <span class="string">&quot;.&quot;</span> + base64UrlEncode(payload), secret)</span><br></pre></td></tr></table></figure><p> 在计算出签名哈希后，JWT头，有效载荷和签名哈希的三个部分组合成一个字符串，每个部分用<code>.</code>分隔，就构成整个JWT对象</p><p> <img src="https://pic.imgdb.cn/item/649a7d5d1ddac507ccab799f.png"></p><p>  注意JWT每部分的作用，在服务端接收到客户端发送过来的JWT token之后：</p><ul><li><code>header</code>和<code>payload</code>可以直接利用base64解码出原文，从<code>header</code>中获取哈希签名的算法，从<code>payload</code>中获取有效数据</li><li><code>signature</code>由于使用了不可逆的加密算法，无法解码出原文，它的作用是<code>校验token有没有被篡改</code>。服务端获取<code>header</code>中的加密算法之后，利用该算法加上<code>secretKey</code>对<code>header</code>、<code>payload</code>进行加密，比对加密后的数据和客户端发送过来的是否一致。注意<code>secretKey</code>只能保存在服务端，而且对于不同的加密算法其含义有所不同，一般对于MD5类型的摘要加密算法，<code>secretKey</code>实际上代表的是盐值</li></ul><h2 id="JWT的种类"><a href="#JWT的种类" class="headerlink" title="JWT的种类"></a>JWT的种类</h2><p> 其实JWT(JSON Web Token)指的是一种规范，这种规范允许我们使用JWT在两个组织之间传递安全可靠的信息，JWT的具体实现可以分为以下几种：</p><ul><li><code>nonsecure JWT</code>：未经过签名，不安全的JWT</li><li><code>JWS</code>：经过签名的JWT</li><li><code>JWE</code>：<code>payload</code>部分经过加密的JWT</li></ul><h2 id="1-nonsecure-JWT"><a href="#1-nonsecure-JWT" class="headerlink" title="1.nonsecure JWT"></a>1.nonsecure JWT</h2><p> 未经过签名，不安全的JWT。其<code>header</code>部分没有指定签名算法{</p> <figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;alg&quot;</span><span class="punctuation">:</span> <span class="string">&quot;none&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;typ&quot;</span><span class="punctuation">:</span> <span class="string">&quot;JWT&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p> 并且也没有<code>Signature</code>部分</p><h2 id="2-JWS"><a href="#2-JWS" class="headerlink" title="2.JWS"></a>2.JWS</h2><p> JWS ，也就是JWT Signature，其结构就是在之前nonsecure JWT的基础上，在头部声明签名算法，并在最后添加上签名。<code>创建签名，是保证jwt不能被他人随意篡改</code>。我们通常使用的JWT一般都是JWS</p><p> 为了完成签名，除了用到header信息和payload信息外，还需要算法的密钥，也就是<code>secretKey</code>。加密的算法一般有2类：</p><ul><li>对称加密：<code>secretKey</code>指加密密钥，可以生成签名与验签</li><li>非对称加密：<code>secretKey</code>指私钥，只用来生成签名，不能用来验签(验签用的是公钥)</li></ul><p> JWT的密钥或者密钥对，一般统一称为JSON Web Key，也就是<code>JWK</code></p><p> 到目前为止，jwt的签名算法有三种：</p><ul><li>HMAC【哈希消息验证码(对称)】：HS256&#x2F;HS384&#x2F;HS512</li><li>RSASSA【RSA签名算法(非对称)】（RS256&#x2F;RS384&#x2F;RS512）</li><li>ECDSA【椭圆曲线数据签名算法(非对称)】（ES256&#x2F;ES384&#x2F;ES512）</li></ul><h2 id="Java中使用JWT"><a href="#Java中使用JWT" class="headerlink" title="Java中使用JWT"></a>Java中使用JWT</h2><p> 官网推荐了6个Java使用JWT的开源库，其中比较推荐使用的是<code>java-jwt</code>和<code>jjwt-root</code></p><p> <img src="https://pic.imgdb.cn/item/649a7dea1ddac507ccacef30.png"></p><h2 id="1-java-jwt"><a href="#1-java-jwt" class="headerlink" title="1.java-jwt"></a>1.java-jwt</h2><h3 id="1-1-对称签名"><a href="#1-1-对称签名" class="headerlink" title="1.1 对称签名"></a>1.1 对称签名</h3><p> 首先引入依赖</p> <figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>com.auth0<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>java-jwt<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>3.10.3<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure><p> 生成JWT的token</p> <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">JWTTest</span> &#123;</span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">testGenerateToken</span><span class="params">()</span>&#123;</span><br><span class="line">        <span class="comment">// 指定token过期时间为10秒</span></span><br><span class="line">        <span class="type">Calendar</span> <span class="variable">calendar</span> <span class="operator">=</span> Calendar.getInstance();</span><br><span class="line">        calendar.add(Calendar.SECOND, <span class="number">10</span>);</span><br><span class="line"></span><br><span class="line">        <span class="type">String</span> <span class="variable">token</span> <span class="operator">=</span> JWT.create()</span><br><span class="line">                .withHeader(<span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;())  <span class="comment">// Header</span></span><br><span class="line">                .withClaim(<span class="string">&quot;userId&quot;</span>, <span class="number">21</span>)  <span class="comment">// Payload</span></span><br><span class="line">                .withClaim(<span class="string">&quot;userName&quot;</span>, <span class="string">&quot;baobao&quot;</span>)</span><br><span class="line">                .withExpiresAt(calendar.getTime())  <span class="comment">// 过期时间</span></span><br><span class="line">                .sign(Algorithm.HMAC256(<span class="string">&quot;!34ADAS&quot;</span>));  <span class="comment">// 签名用的secret</span></span><br><span class="line"></span><br><span class="line">        System.out.println(token);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p> <img src="https://pic.imgdb.cn/item/649a7f5e1ddac507ccb0970f.png"></p><p>  注意<code>多次运行方法生成的token字符串内容是不一样的</code>，尽管我们的payload信息没有变动。因为<code>JWT中携带了超时时间</code>，所以每次生成的token会不一样，我们利用base64解密工具可以发现payload确实携带了超时时间</p><p>  <img src="https://pic.imgdb.cn/item/649a7f7c1ddac507ccb0c510.png"></p><p> 解析JWT字符串</p> <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Test</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">testResolveToken</span><span class="params">()</span>&#123;</span><br><span class="line">    <span class="comment">// 创建解析对象，使用的算法和secret要与创建token时保持一致</span></span><br><span class="line">    <span class="type">JWTVerifier</span> <span class="variable">jwtVerifier</span> <span class="operator">=</span> JWT.require(Algorithm.HMAC256(<span class="string">&quot;!34ADAS&quot;</span>)).build();</span><br><span class="line">    <span class="comment">// 解析指定的token</span></span><br><span class="line">    <span class="type">DecodedJWT</span> <span class="variable">decodedJWT</span> <span class="operator">=</span> jwtVerifier.verify(<span class="string">&quot;eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyTmFtZSI6ImJhb2JhbyIsImV4cCI6MTU5OTkyMjUyOCwidXNlcklkIjoyMX0.YhA3kh9KZOAb7om1C7o3vBhYp0f61mhQWWOoCrrhqvo&quot;</span>);</span><br><span class="line">    <span class="comment">// 获取解析后的token中的payload信息</span></span><br><span class="line">    <span class="type">Claim</span> <span class="variable">userId</span> <span class="operator">=</span> decodedJWT.getClaim(<span class="string">&quot;userId&quot;</span>);</span><br><span class="line">    <span class="type">Claim</span> <span class="variable">userName</span> <span class="operator">=</span> decodedJWT.getClaim(<span class="string">&quot;userName&quot;</span>);</span><br><span class="line">    System.out.println(userId.asInt());</span><br><span class="line">    System.out.println(userName.asString());</span><br><span class="line">    <span class="comment">// 输出超时时间</span></span><br><span class="line">    System.out.println(decodedJWT.getExpiresAt());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p> 运行后发现报异常，原因是之前生成的token已经过期</p><p> <img src="https://pic.imgdb.cn/item/649a7fae1ddac507ccb10252.png"></p><p> 再运行一次生成token的方法，然后在过期时间10秒之内将生成的字符串拷贝到解析方法中，运行解析方法即可成功</p><p> <img src="https://pic.imgdb.cn/item/649a803a1ddac507ccb1deb3.png"></p><p> 我们可以将上述方法封装成工具类</p> <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">JWTUtils</span> &#123;</span><br><span class="line">    <span class="comment">// 签名密钥</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">SECRET</span> <span class="operator">=</span> <span class="string">&quot;!DAR$&quot;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 生成token</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> payload token携带的信息</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> token字符串</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> String <span class="title function_">getToken</span><span class="params">(Map&lt;String,String&gt; payload)</span>&#123;</span><br><span class="line">        <span class="comment">// 指定token过期时间为7天</span></span><br><span class="line">        <span class="type">Calendar</span> <span class="variable">calendar</span> <span class="operator">=</span> Calendar.getInstance();</span><br><span class="line">        calendar.add(Calendar.DATE, <span class="number">7</span>);</span><br><span class="line"></span><br><span class="line">        JWTCreator.<span class="type">Builder</span> <span class="variable">builder</span> <span class="operator">=</span> JWT.create();</span><br><span class="line">        <span class="comment">// 构建payload</span></span><br><span class="line">        payload.forEach((k,v) -&gt; builder.withClaim(k,v));</span><br><span class="line">        <span class="comment">// 指定过期时间和签名算法</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">token</span> <span class="operator">=</span> builder.withExpiresAt(calendar.getTime()).sign(Algorithm.HMAC256(SECRET));</span><br><span class="line">        <span class="keyword">return</span> token;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 解析token</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> token token字符串</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> 解析后的token</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> DecodedJWT <span class="title function_">decode</span><span class="params">(String token)</span>&#123;</span><br><span class="line">        <span class="type">JWTVerifier</span> <span class="variable">jwtVerifier</span> <span class="operator">=</span> JWT.require(Algorithm.HMAC256(SECRET)).build();</span><br><span class="line">        <span class="type">DecodedJWT</span> <span class="variable">decodedJWT</span> <span class="operator">=</span> jwtVerifier.verify(token);</span><br><span class="line">        <span class="keyword">return</span> decodedJWT;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="1-2-非对称签名"><a href="#1-2-非对称签名" class="headerlink" title="1.2 非对称签名"></a>1.2 非对称签名</h3><p> 生成jwt串的时候需要指定私钥，解析jwt串的时候需要指定公钥</p> <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">RSA_PRIVATE_KEY</span> <span class="operator">=</span> <span class="string">&quot;...&quot;</span>;</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">RSA_PUBLIC_KEY</span> <span class="operator">=</span> <span class="string">&quot;...&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 生成token</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> payload token携带的信息</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> token字符串</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> String <span class="title function_">getTokenRsa</span><span class="params">(Map&lt;String,String&gt; payload)</span>&#123;</span><br><span class="line">    <span class="comment">// 指定token过期时间为7天</span></span><br><span class="line">    <span class="type">Calendar</span> <span class="variable">calendar</span> <span class="operator">=</span> Calendar.getInstance();</span><br><span class="line">    calendar.add(Calendar.DATE, <span class="number">7</span>);</span><br><span class="line"></span><br><span class="line">    JWTCreator.<span class="type">Builder</span> <span class="variable">builder</span> <span class="operator">=</span> JWT.create();</span><br><span class="line">    <span class="comment">// 构建payload</span></span><br><span class="line">    payload.forEach((k,v) -&gt; builder.withClaim(k,v));</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 利用hutool创建RSA</span></span><br><span class="line">    <span class="type">RSA</span> <span class="variable">rsa</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">RSA</span>(RSA_PRIVATE_KEY, <span class="literal">null</span>);</span><br><span class="line">    <span class="comment">// 获取私钥</span></span><br><span class="line">    <span class="type">RSAPrivateKey</span> <span class="variable">privateKey</span> <span class="operator">=</span> (RSAPrivateKey) rsa.getPrivateKey();</span><br><span class="line">    <span class="comment">// 签名时传入私钥</span></span><br><span class="line">    <span class="type">String</span> <span class="variable">token</span> <span class="operator">=</span> builder.withExpiresAt(calendar.getTime()).sign(Algorithm.RSA256(<span class="literal">null</span>, privateKey));</span><br><span class="line">    <span class="keyword">return</span> token;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 解析token</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> token token字符串</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> 解析后的token</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> DecodedJWT <span class="title function_">decodeRsa</span><span class="params">(String token)</span>&#123;</span><br><span class="line">    <span class="comment">// 利用hutool创建RSA</span></span><br><span class="line">    <span class="type">RSA</span> <span class="variable">rsa</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">RSA</span>(<span class="literal">null</span>, RSA_PUBLIC_KEY);</span><br><span class="line">    <span class="comment">// 获取RSA公钥</span></span><br><span class="line">    <span class="type">RSAPublicKey</span> <span class="variable">publicKey</span> <span class="operator">=</span> (RSAPublicKey) rsa.getPublicKey();</span><br><span class="line">    <span class="comment">// 验签时传入公钥</span></span><br><span class="line">    <span class="type">JWTVerifier</span> <span class="variable">jwtVerifier</span> <span class="operator">=</span> JWT.require(Algorithm.RSA256(publicKey, <span class="literal">null</span>)).build();</span><br><span class="line">    <span class="type">DecodedJWT</span> <span class="variable">decodedJWT</span> <span class="operator">=</span> jwtVerifier.verify(token);</span><br><span class="line">    <span class="keyword">return</span> decodedJWT;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="2-jjwt-root"><a href="#2-jjwt-root" class="headerlink" title="2.jjwt-root"></a>2.jjwt-root</h2><h3 id="2-1-对称签名"><a href="#2-1-对称签名" class="headerlink" title="2.1 对称签名"></a>2.1 对称签名</h3><p> 引入依赖</p> <figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>io.jsonwebtoken<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>jjwt<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>0.9.1<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br><span class="line"></span><br></pre></td></tr></table></figure><p> 使用方法类似，可参考下列代码</p> <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">JwtUtils</span> &#123;</span><br><span class="line">    <span class="comment">// token时效：24小时</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">long</span> <span class="variable">EXPIRE</span> <span class="operator">=</span> <span class="number">1000</span> * <span class="number">60</span> * <span class="number">60</span> * <span class="number">24</span>;</span><br><span class="line">    <span class="comment">// 签名哈希的密钥，对于不同的加密算法来说含义不同</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">APP_SECRET</span> <span class="operator">=</span> <span class="string">&quot;ukc8BDbRigUDaY6pZFfWus2jZWLPHO&quot;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 根据用户id和昵称生成token</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> id  用户id</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> nickname 用户昵称</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> JWT规则生成的token</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> String <span class="title function_">getJwtToken</span><span class="params">(String id, String nickname)</span>&#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">JwtToken</span> <span class="operator">=</span> Jwts.builder()</span><br><span class="line">                .setHeaderParam(<span class="string">&quot;typ&quot;</span>, <span class="string">&quot;JWT&quot;</span>)</span><br><span class="line">                .setHeaderParam(<span class="string">&quot;alg&quot;</span>, <span class="string">&quot;HS256&quot;</span>)</span><br><span class="line">                .setSubject(<span class="string">&quot;baobao-user&quot;</span>)</span><br><span class="line">                .setIssuedAt(<span class="keyword">new</span> <span class="title class_">Date</span>())</span><br><span class="line">                .setExpiration(<span class="keyword">new</span> <span class="title class_">Date</span>(System.currentTimeMillis() + EXPIRE))</span><br><span class="line">                .claim(<span class="string">&quot;id&quot;</span>, id)</span><br><span class="line">                .claim(<span class="string">&quot;nickname&quot;</span>, nickname)</span><br><span class="line">            <span class="comment">// HS256算法实际上就是MD5加盐值，此时APP_SECRET就代表盐值</span></span><br><span class="line">                .signWith(SignatureAlgorithm.HS256, APP_SECRET)</span><br><span class="line">                .compact();</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> JwtToken;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 判断token是否存在与有效</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> jwtToken token字符串</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> 如果token有效返回true，否则返回false</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="type">boolean</span> <span class="title function_">checkToken</span><span class="params">(String jwtToken)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span>(StringUtils.isEmpty(jwtToken)) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 判断token是否存在与有效</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> request Http请求对象</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> 如果token有效返回true，否则返回false</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="type">boolean</span> <span class="title function_">checkToken</span><span class="params">(HttpServletRequest request)</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">// 从http请求头中获取token字符串</span></span><br><span class="line">            <span class="type">String</span> <span class="variable">jwtToken</span> <span class="operator">=</span> request.getHeader(<span class="string">&quot;token&quot;</span>);</span><br><span class="line">            <span class="keyword">if</span>(StringUtils.isEmpty(jwtToken)) <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">            Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 根据token获取会员id</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> request Http请求对象</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> 解析token后获得的用户id</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> String <span class="title function_">getMemberIdByJwtToken</span><span class="params">(HttpServletRequest request)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">jwtToken</span> <span class="operator">=</span> request.getHeader(<span class="string">&quot;token&quot;</span>);</span><br><span class="line">        <span class="keyword">if</span>(StringUtils.isEmpty(jwtToken)) <span class="keyword">return</span> <span class="string">&quot;&quot;</span>;</span><br><span class="line">        Jws&lt;Claims&gt; claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);</span><br><span class="line">        <span class="type">Claims</span> <span class="variable">claims</span> <span class="operator">=</span> claimsJws.getBody();</span><br><span class="line">        <span class="keyword">return</span> (String)claims.get(<span class="string">&quot;id&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>  注意：</p><ul><li>jjwt在0.10版本以后发生了较大变化，pom依赖要引入多个</li></ul>  <figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>io.jsonwebtoken<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>jjwt-api<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>0.11.2<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>io.jsonwebtoken<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>jjwt-impl<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>0.11.2<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">scope</span>&gt;</span>runtime<span class="tag">&lt;/<span class="name">scope</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>io.jsonwebtoken<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>jjwt-jackson<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span> <span class="comment">&lt;!-- or jjwt-gson if Gson is preferred --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">version</span>&gt;</span>0.11.2<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">scope</span>&gt;</span>runtime<span class="tag">&lt;/<span class="name">scope</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><ul><li><p>标准规范中对各种加密算法的<code>secretKey</code>的长度有如下要求：</p><ul><li><code>HS256</code>：要求至少 256 bits (32 bytes)</li><li><code>HS384</code>：要求至少384 bits (48 bytes)</li><li><code>HS512</code>：要求至少512 bits (64 bytes)</li><li><code>RS256</code> and <code>PS256</code>：至少2048 bits</li><li><code>RS384</code> and <code>PS384</code>：至少3072 bits</li><li><code>RS512</code> and <code>PS512</code>：至少4096 bits</li><li><code>ES256</code>：至少256 bits (32 bytes)</li><li><code>ES384</code>：至少384 bits (48 bytes)</li><li><code>ES512</code>：至少512 bits (64 bytes)</li></ul><p>在jjwt0.10版本之前，没有强制要求，<code>secretKey</code>长度不满足要求时也可以签名成功。但是0.10版本后强制要求<code>secretKey</code>满足规范中的长度要求，否则生成jws时会抛出异常</p><p><img src="https://pic.imgdb.cn/item/649a818e1ddac507ccb3f812.png"></p></li><li><p>新版本的jjwt中，之前的签名和验签方法都是传入密钥的字符串，已经过时。最新的方法需要传入<code>Key</code>对象</p></li></ul> <figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">JwtUtils</span> &#123;</span><br><span class="line">    <span class="comment">// token时效：24小时</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">long</span> <span class="variable">EXPIRE</span> <span class="operator">=</span> <span class="number">1000</span> * <span class="number">60</span> * <span class="number">60</span> * <span class="number">24</span>;</span><br><span class="line">    <span class="comment">// 签名哈希的密钥，对于不同的加密算法来说含义不同</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">APP_SECRET</span> <span class="operator">=</span> <span class="string">&quot;ukc8BDbRigUDaY6pZFfWus2jZWLPHOsdadasdasfdssfeweee&quot;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 根据用户id和昵称生成token</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> id  用户id</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> nickname 用户昵称</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> JWT规则生成的token</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> String <span class="title function_">getJwtToken</span><span class="params">(String id, String nickname)</span>&#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">JwtToken</span> <span class="operator">=</span> Jwts.builder()</span><br><span class="line">                .setSubject(<span class="string">&quot;baobao-user&quot;</span>)</span><br><span class="line">                .setIssuedAt(<span class="keyword">new</span> <span class="title class_">Date</span>())</span><br><span class="line">                .setExpiration(<span class="keyword">new</span> <span class="title class_">Date</span>(System.currentTimeMillis() + EXPIRE))</span><br><span class="line">                .claim(<span class="string">&quot;id&quot;</span>, id)</span><br><span class="line">                .claim(<span class="string">&quot;nickname&quot;</span>, nickname)</span><br><span class="line">                <span class="comment">// 传入Key对象</span></span><br><span class="line">                .signWith(Keys.hmacShaKeyFor(APP_SECRET.getBytes(StandardCharsets.UTF_8)), SignatureAlgorithm.HS256)</span><br><span class="line">                .compact();</span><br><span class="line">        <span class="keyword">return</span> JwtToken;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 判断token是否存在与有效</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> jwtToken token字符串</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> 如果token有效返回true，否则返回false</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> Jws&lt;Claims&gt; <span class="title function_">decode</span><span class="params">(String jwtToken)</span> &#123;</span><br><span class="line">        <span class="comment">// 传入Key对象</span></span><br><span class="line">        Jws&lt;Claims&gt; claimsJws = Jwts.parserBuilder().setSigningKey(Keys.hmacShaKeyFor(APP_SECRET.getBytes(StandardCharsets.UTF_8))).build().parseClaimsJws(jwtToken);</span><br><span class="line">        <span class="keyword">return</span> claimsJws;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="2-2-非对称签名"><a href="#2-2-非对称签名" class="headerlink" title="2.2 非对称签名"></a>2.2 非对称签名</h3><p> 生成jwt串的时候需要指定私钥，解析jwt串的时候需要指定公钥</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">RSA_PRIVATE_KEY</span> <span class="operator">=</span> <span class="string">&quot;...&quot;</span>;</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">RSA_PUBLIC_KEY</span> <span class="operator">=</span> <span class="string">&quot;...&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 根据用户id和昵称生成token</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> id  用户id</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> nickname 用户昵称</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> JWT规则生成的token</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> String <span class="title function_">getJwtTokenRsa</span><span class="params">(String id, String nickname)</span>&#123;</span><br><span class="line">    <span class="comment">// 利用hutool创建RSA</span></span><br><span class="line">    <span class="type">RSA</span> <span class="variable">rsa</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">RSA</span>(RSA_PRIVATE_KEY, <span class="literal">null</span>);</span><br><span class="line">    <span class="type">RSAPrivateKey</span> <span class="variable">privateKey</span> <span class="operator">=</span> (RSAPrivateKey) rsa.getPrivateKey();</span><br><span class="line">    <span class="type">String</span> <span class="variable">JwtToken</span> <span class="operator">=</span> Jwts.builder()</span><br><span class="line">        .setSubject(<span class="string">&quot;baobao-user&quot;</span>)</span><br><span class="line">        .setIssuedAt(<span class="keyword">new</span> <span class="title class_">Date</span>())</span><br><span class="line">        .setExpiration(<span class="keyword">new</span> <span class="title class_">Date</span>(System.currentTimeMillis() + EXPIRE))</span><br><span class="line">        .claim(<span class="string">&quot;id&quot;</span>, id)</span><br><span class="line">        .claim(<span class="string">&quot;nickname&quot;</span>, nickname)</span><br><span class="line">        <span class="comment">// 签名指定私钥</span></span><br><span class="line">        .signWith(privateKey, SignatureAlgorithm.RS256)</span><br><span class="line">        .compact();</span><br><span class="line">    <span class="keyword">return</span> JwtToken;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 判断token是否存在与有效</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> jwtToken token字符串</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> 如果token有效返回true，否则返回false</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> Jws&lt;Claims&gt; <span class="title function_">decodeRsa</span><span class="params">(String jwtToken)</span> &#123;</span><br><span class="line">    <span class="type">RSA</span> <span class="variable">rsa</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">RSA</span>(<span class="literal">null</span>, RSA_PUBLIC_KEY);</span><br><span class="line">    <span class="type">RSAPublicKey</span> <span class="variable">publicKey</span> <span class="operator">=</span> (RSAPublicKey) rsa.getPublicKey();</span><br><span class="line">    <span class="comment">// 验签指定公钥</span></span><br><span class="line">    Jws&lt;Claims&gt; claimsJws = Jwts.parserBuilder().setSigningKey(publicKey).build().parseClaimsJws(jwtToken);</span><br><span class="line">    <span class="keyword">return</span> claimsJws;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="实际开发中的应用"><a href="#实际开发中的应用" class="headerlink" title="实际开发中的应用"></a>实际开发中的应用</h2><p> 在实际的<code>SpringBoot</code>项目中，一般我们可以用如下流程做登录：</p><ol><li>在登录验证通过后，给用户生成一个对应的随机token(注意这个token不是指jwt，可以用uuid等算法生成)，然后将这个token作为key的一部分，用户信息作为value存入Redis，并设置过期时间，这个过期时间就是登录失效的时间</li><li>将第1步中生成的随机token作为JWT的payload生成JWT字符串返回给前端</li><li>前端之后每次请求都在请求头中的<code>Authorization</code>字段中携带JWT字符串</li><li>后端定义一个拦截器，每次收到前端请求时，都先从请求头中的<code>Authorization</code>字段中取出JWT字符串并进行验证，验证通过后解析出payload中的随机token，然后再用这个随机token得到key，从Redis中获取用户信息，如果能获取到就说明用户已经登录</li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">JWTInterceptor</span> <span class="keyword">implements</span> <span class="title class_">HandlerInterceptor</span> &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">preHandle</span><span class="params">(HttpServletRequest request, HttpServletResponse response, Object handler)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">JWT</span> <span class="operator">=</span> request.getHeader(<span class="string">&quot;Authorization&quot;</span>);</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">// 1.校验JWT字符串</span></span><br><span class="line">            <span class="type">DecodedJWT</span> <span class="variable">decodedJWT</span> <span class="operator">=</span> JWTUtils.decode(JWT);</span><br><span class="line">            <span class="comment">// 2.取出JWT字符串载荷中的随机token，从Redis中获取用户信息</span></span><br><span class="line">            ...</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        &#125;<span class="keyword">catch</span> (SignatureVerificationException e)&#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;无效签名&quot;</span>);</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;<span class="keyword">catch</span> (TokenExpiredException e)&#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;token已经过期&quot;</span>);</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;<span class="keyword">catch</span> (AlgorithmMismatchException e)&#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;算法不一致&quot;</span>);</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;<span class="keyword">catch</span> (Exception e)&#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;token无效&quot;</span>);</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>  在实际开发中需要用下列手段来增加JWT的安全性：</p><ul><li>因为JWT是在请求头中传递的，所以为了避免网络劫持，推荐使用<code>HTTPS</code>来传输，更加安全</li><li>JWT的哈希签名的密钥是存放在服务端的，所以只要服务器不被攻破，理论上JWT是安全的。因此要保证服务器的安全</li><li>JWT可以使用暴力穷举来破解，所以为了应对这种破解方式，可以定期更换服务端的哈希签名密钥(相当于盐值)。这样可以保证等破解结果出来了，你的密钥也已经换了</li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;\assets\css\APlayer.min.css&quot;&gt;&lt;script src=&quot;\assets\js\APlayer.min.js&quot; cla</summary>
      
    
    
    
    <category term="Java" scheme="https://nerkeler.github.io/categories/Java/"/>
    
    
    <category term="转载" scheme="https://nerkeler.github.io/tags/%E8%BD%AC%E8%BD%BD/"/>
    
    <category term="JWT" scheme="https://nerkeler.github.io/tags/JWT/"/>
    
  </entry>
  
  <entry>
    <title>EasyExcel 数值型和最后一列超出文本框问题</title>
    <link href="https://nerkeler.github.io/2023/06/27/EasyExcel-%E6%95%B0%E5%80%BC%E5%9E%8B%E5%92%8C%E6%9C%80%E5%90%8E%E4%B8%80%E5%88%97%E8%B6%85%E5%87%BA%E6%96%87%E6%9C%AC%E6%A1%86%E9%97%AE%E9%A2%98/"/>
    <id>https://nerkeler.github.io/2023/06/27/EasyExcel-%E6%95%B0%E5%80%BC%E5%9E%8B%E5%92%8C%E6%9C%80%E5%90%8E%E4%B8%80%E5%88%97%E8%B6%85%E5%87%BA%E6%96%87%E6%9C%AC%E6%A1%86%E9%97%AE%E9%A2%98/</id>
    <published>2023-06-27T02:49:00.000Z</published>
    <updated>2025-05-25T07:22:08.383Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h1 id="EasyExcel-数值型和最后一列超出文本框问题"><a href="#EasyExcel-数值型和最后一列超出文本框问题" class="headerlink" title="EasyExcel 数值型和最后一列超出文本框问题"></a>EasyExcel 数值型和最后一列超出文本框问题</h1><p>Alibaba出品的EasyExcel对比原版excel库POI，极大的简化了开发人员的代码编写工作。EasyExcel使用注解和监听器等简化读写开发，</p><h2 id="问题说明"><a href="#问题说明" class="headerlink" title="问题说明"></a>问题说明</h2><p>在我的日常工作中，很大一部分的工作都是将数据库的数据整理归纳后导出为Excel，当然会碰到许许多多的问题，比如导出超时（当然这个问题大多数都是SQL执行的问题，日常几十万条数据的excel并不会花费多少时间），字段类型与需求不匹配、文本超出单元格、动态头…..</p><p>这里主要记录和说明其中几个问题</p><ul><li>字段类型不匹配</li><li>最后一列文本内容超出单元格</li></ul><h3 id="字段类型不匹配"><a href="#字段类型不匹配" class="headerlink" title="字段类型不匹配"></a>字段类型不匹配</h3><p>解决方法可看easyexcel 官方github Issues  :<a href="https://github.com/alibaba/easyexcel/issues/2409">https://github.com/alibaba/easyexcel/issues/2409</a></p><h4 id="问题展示"><a href="#问题展示" class="headerlink" title="问题展示"></a>问题展示</h4><p><img src="https://pic.imgdb.cn/item/6492b9221ddac507cce8d399.png"></p><p>如上图所示，当我们设置实体类的属性为字符串型时，默认导出的时候数值型会模糊单元格的格式，所以上方有个三角符号，这时候excel是无法进行数值的求和汇总等运算的。</p><h4 id="分析"><a href="#分析" class="headerlink" title="分析"></a>分析</h4><p>我们看一下导出excel时的实体类</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Student</span> <span class="keyword">implements</span> <span class="title class_">Serializable</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@ExcelProperty( &quot;学生编号&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String id;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@ExcelProperty( &quot;学生姓名&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String name;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@ExcelProperty( &quot;学生生日&quot;)</span></span><br><span class="line">    <span class="meta">@DateTimeFormat(&quot;yyyy-mm-dd&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> Date birthday;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@ExcelProperty(&quot;学号&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String stuNo;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>注意 <code>private String stuNo;</code>为String类型，这里就是导致导出excel无法确定单元格格式的主要原因。</p><h4 id="解决方法"><a href="#解决方法" class="headerlink" title="解决方法"></a>解决方法</h4><p>修改方式很简单，将String改为对应的数值型格式即可如</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Student</span> <span class="keyword">implements</span> <span class="title class_">Serializable</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@ExcelProperty( &quot;学生编号&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String id;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@ExcelProperty( &quot;学生姓名&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String name;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@ExcelProperty( &quot;学生生日&quot;)</span></span><br><span class="line">    <span class="meta">@DateTimeFormat(&quot;yyyy-mm-dd&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> Date birthday;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@ExcelProperty(&quot;学号&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> Integer stuNo;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>结果</p><p><img src="https://pic.imgdb.cn/item/649a3a3e1ddac507cc3e6c52.png"></p><p><em>PS: 第一列仍为String类型，所以导出仍有三角符号。</em></p><h3 id="最后一列文本内容超出单元格"><a href="#最后一列文本内容超出单元格" class="headerlink" title="最后一列文本内容超出单元格"></a>最后一列文本内容超出单元格</h3><p>这个问题我在issues 中没有找到，可能问题比较简单:joy::joy::joy:</p><p><strong>先说一下思路，构造出一列空值覆盖前一列内容超出部分，再手动生成标题头</strong></p><h4 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h4><p>如果最后一列导出为长文本字符型时，很容易超出单元格，观感不佳，现象如下。</p><p><img src="https://pic.imgdb.cn/item/649a3bcb1ddac507cc40a317.png"></p><h4 id="分析-1"><a href="#分析-1" class="headerlink" title="分析"></a>分析</h4><p>我们看一下实体类多加了一个note字段，这个也就是上方长文本的字段。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Student</span> <span class="keyword">implements</span> <span class="title class_">Serializable</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@ExcelProperty( &quot;学生编号&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String id;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@ExcelProperty( &quot;学生姓名&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String name;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@ExcelProperty( &quot;学生生日&quot;)</span></span><br><span class="line">    <span class="meta">@DateTimeFormat(&quot;yyyy-mm-dd&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> Date birthday;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@ExcelProperty(&quot;学号&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> Integer stuNo;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@ExcelProperty(&quot;备注&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String note;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="解决方法-1"><a href="#解决方法-1" class="headerlink" title="解决方法"></a>解决方法</h4><p>我们首先看一下导出代码</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">EasyExcel.write(<span class="string">&quot;学生信息表.xlsx&quot;</span>, Student.class)</span><br><span class="line">        .sheet()</span><br><span class="line">        .head(Student.class)</span><br><span class="line">        .doWrite(WriteDemo.getData());</span><br></pre></td></tr></table></figure><p>注意一下其中的方法和参数write()和head()</p><p><strong>write()重载方法入参</strong></p><ul><li>File  file</li><li>File file,@Nullable Class head</li><li>String pathName</li><li>String pathName, @Nullable Class head</li><li>outputStream outputStream</li><li>outputStream outputStream, @Nullable Class head</li></ul><p>所有的重载方法入参第一个都是确定文件名称（File  ）和位置（pathName），outputStream 是web上传、下载。我们关注后面一个参数 <code>Class head</code>这里指定了导出文件的head，同head方法功能相似，也就是说，这里我们去到  <code>.head(Student.class)</code>或者<code>.write(&quot;学生信息表.xlsx&quot;, Student.class)</code>中的<code>Student.class</code>都是正常的。</p><h5 id="简单分析一下源代码"><a href="#简单分析一下源代码" class="headerlink" title="简单分析一下源代码"></a>简单分析一下源代码</h5><h5 id="write"><a href="#write" class="headerlink" title="write"></a>write</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">EasyExcelFactory</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> ExcelWriterBuilder <span class="title function_">write</span><span class="params">(String pathName, Class head)</span> &#123;</span><br><span class="line">        <span class="type">ExcelWriterBuilder</span> <span class="variable">excelWriterBuilder</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ExcelWriterBuilder</span>();</span><br><span class="line">        excelWriterBuilder.file(pathName);</span><br><span class="line">        <span class="keyword">if</span> (head != <span class="literal">null</span>) &#123;</span><br><span class="line">            excelWriterBuilder.head(head);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> excelWriterBuilder;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title class_">AbstractParameterBuilder</span>&lt;T <span class="keyword">extends</span> <span class="title class_">AbstractParameterBuilder</span>, C <span class="keyword">extends</span> <span class="title class_">BasicParameter</span>&gt; &#123;</span><br><span class="line"> </span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> T <span class="title function_">head</span><span class="params">(Class clazz)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.parameter().setClazz(clazz);</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">this</span>.self();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>我们可以看到这里给<code>excelWriterBuilder</code>设置了一个<code>head</code>然后调用了<code>AbstractParameterBuilder</code>的head方法</p><h5 id="head"><a href="#head" class="headerlink" title="head"></a>head</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title class_">AbstractParameterBuilder</span>&lt;T <span class="keyword">extends</span> <span class="title class_">AbstractParameterBuilder</span>, C <span class="keyword">extends</span> <span class="title class_">BasicParameter</span>&gt; &#123;</span><br><span class="line"> <span class="comment">/***/</span></span><br><span class="line">    <span class="keyword">public</span> T <span class="title function_">head</span><span class="params">(Class clazz)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.parameter().setClazz(clazz);</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">this</span>.self();</span><br><span class="line">    &#125;</span><br><span class="line">     <span class="comment">/***/</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">abstract</span> C <span class="title function_">parameter</span><span class="params">()</span>;</span><br><span class="line">     <span class="comment">/***/</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里给 <code>this.parameter()</code>设置了类名，这里的this.parameter指的是<code>AbstractParameterBuilder</code>  中的<code>protected abstract C parameter();</code>方法，我们可以看到，这里其实本质上是调用了同一个方法。</p><h5 id="head的特别之处"><a href="#head的特别之处" class="headerlink" title="head的特别之处"></a>head的特别之处</h5><p>head方法本质上是用来动态生成头文件的，详情可参照官方文档<a href="https://easyexcel.opensource.alibaba.com/docs/current/quickstart/write#%E5%8A%A8%E6%80%81%E5%A4%B4%E5%AE%9E%E6%97%B6%E7%94%9F%E6%88%90%E5%A4%B4%E5%86%99%E5%85%A5">https://easyexcel.opensource.alibaba.com/docs/current/quickstart/write#%E5%8A%A8%E6%80%81%E5%A4%B4%E5%AE%9E%E6%97%B6%E7%94%9F%E6%88%90%E5%A4%B4%E5%86%99%E5%85%A5</a></p><p>如果我们再write方法上没有指定标题头，则可以手动通过head方法构造，这样我们就可以实现最后一列不出单元格了</p><h5 id="具体实现"><a href="#具体实现" class="headerlink" title="具体实现"></a>具体实现</h5><p>先修改导出实体类添加一个占位符<strong>并赋初始值为空</strong>（<em><strong>注意，如果没有赋值为空字符串，则不会写入excel进行覆盖前一列</strong></em>）</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Student</span> <span class="keyword">implements</span> <span class="title class_">Serializable</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@ExcelProperty( &quot;学生编号&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String id;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@ExcelProperty( &quot;学生姓名&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String name;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@ExcelProperty( &quot;学生生日&quot;)</span></span><br><span class="line">    <span class="meta">@DateTimeFormat(&quot;yyyy-mm-dd&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> Date birthday;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@ExcelProperty(&quot;学号&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> Integer stuNo;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@ExcelProperty(&quot;备注&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> String note;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 占位符</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">String</span> <span class="variable">palaceHolder</span> <span class="operator">=</span> <span class="string">&quot;&quot;</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>手动构造头文件</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> List&lt;List&lt;String&gt;&gt; <span class="title function_">setHead</span><span class="params">()</span> &#123;</span><br><span class="line"></span><br><span class="line">    List&lt;String&gt; list = Arrays.asList(</span><br><span class="line">            <span class="string">&quot;学生编号&quot;</span>,</span><br><span class="line">            <span class="string">&quot;学生姓名&quot;</span>,</span><br><span class="line">            <span class="string">&quot;学生生日&quot;</span>,</span><br><span class="line">            <span class="string">&quot;学号&quot;</span>,</span><br><span class="line">            <span class="string">&quot;备注&quot;</span></span><br><span class="line">    );</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> list.stream()</span><br><span class="line">            .map(Arrays::asList)</span><br><span class="line">            .collect(Collectors.toList());</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>最后生成excel</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">dynamicHeadWrite</span><span class="params">()</span> &#123;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    EasyExcel.write(<span class="string">&quot;学生信息表.xlsx&quot;</span>)</span><br><span class="line">            .sheet()</span><br><span class="line">            .head(setHead())</span><br><span class="line">            .doWrite(WriteDemo.getData());</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>结果</p><p><img src="https://pic.imgdb.cn/item/649a4d1c1ddac507cc5d38dc.png"></p><p>可以看到，最后一列内容没有超出单元格了。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;\assets\css\APlayer.min.css&quot;&gt;&lt;script src=&quot;\assets\js\APlayer.min.js&quot; cla</summary>
      
    
    
    
    <category term="Java" scheme="https://nerkeler.github.io/categories/Java/"/>
    
    
    <category term="easyexcel" scheme="https://nerkeler.github.io/tags/easyexcel/"/>
    
    <category term="java" scheme="https://nerkeler.github.io/tags/java/"/>
    
  </entry>
  
  <entry>
    <title> Java中的并发工具类-CountDownLatch和CyclicBarrier</title>
    <link href="https://nerkeler.github.io/2023/06/16/Java%E4%B8%AD%E7%9A%84%E5%B9%B6%E5%8F%91%E5%B7%A5%E5%85%B7%E7%B1%BB-CountDownLatch%E5%92%8CCyclicBarrier/"/>
    <id>https://nerkeler.github.io/2023/06/16/Java%E4%B8%AD%E7%9A%84%E5%B9%B6%E5%8F%91%E5%B7%A5%E5%85%B7%E7%B1%BB-CountDownLatch%E5%92%8CCyclicBarrier/</id>
    <published>2023-06-16T08:34:00.000Z</published>
    <updated>2024-01-04T01:49:49.425Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h1 id="Java中的并发工具类-CountDownLatch和CyclicBarrier"><a href="#Java中的并发工具类-CountDownLatch和CyclicBarrier" class="headerlink" title="Java中的并发工具类-CountDownLatch和CyclicBarrier"></a>Java中的并发工具类-CountDownLatch和CyclicBarrier</h1><p>在JDK的并发包中，有几个非常有用的并发工具类，它们分别是：<code>CountDownLatch</code>、<code>CyclicBarrier</code>、<code>Semaphore</code>和<code>Exchanger</code>。</p><ul><li><code>CountDownLatch</code>（倒计时门闩）：它允许一个或多个线程等待其他线程完成操作后再继续执行。它通过一个计数器来实现，线程通过调用 <code>countDown()</code> 方法来减少计数器的值，<code>await()</code>方法进行阻塞等待计数器减少，当计数器达到零时，等待的线程将被释放。</li><li><code>CyclicBarrier</code>（循环屏障）：它允许一组线程互相等待，直到到达一个共同的屏障点，然后继续执行后续操作。与<code>CountDownLatch</code>不同的是，<code>CyclicBarrier</code>的计数器可以重复使用（<code>reset()</code>方法），当所有等待线程都到达屏障点后，计数器会重置，线程可以继续下一次等待。</li><li><code>Semaphore</code>（信号量）：它用于控制对某个资源的访问权限。<code>Semaphore</code>维护了一组许可证，线程在访问资源前需要获取许可证，如果许可证不可用，则线程必须等待，直到有可用的许可证。</li><li><code>Exchanger</code>（交换器）：它提供了一种线程间交换数据的机制。两个线程可以通过<code>Exchanger</code>交换数据，当两个线程都调用 <code>exchange()</code> 方法后，他们会彼此交换数据，并继续执行后续操作。</li></ul><h2 id="CountDownLatch"><a href="#CountDownLatch" class="headerlink" title="CountDownLatch"></a>CountDownLatch</h2><h3 id="Latch（门闩）设计模式"><a href="#Latch（门闩）设计模式" class="headerlink" title="Latch（门闩）设计模式"></a><code>Latch</code>（门闩）设计模式</h3><p>当多个线程并发执行任务，然后只有等待所有子任务全部完成进行汇总，程序的门闩才能打开让程序继续往下执行。它指定了一个屏障，只有所有条件都满足的时候，门阀才能打开。</p><p>比如小明和小红相约周末去爬山，约定在人民广场碰头，然后一同出发去爬山，他们各自从家里出发，无论是其中某一个先到达了人民广场都要等待另一个到达之后才可以继续进行下去，这里的人民广场碰头就相当于上述的门闩。</p><p><img src="https://pic.imgdb.cn/item/648bbc501ddac507cc564792.png"></p><h3 id="示例"><a href="#示例" class="headerlink" title="示例"></a>示例</h3><p>还是使用上面的例子，我们模拟小明和小红从家出发，设定不同的等待时间模拟到达人民广场的路程耗时。代码如下</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> InterruptedException, ExecutionException &#123;</span><br><span class="line">        <span class="keyword">final</span> <span class="type">int</span> <span class="variable">threadNum</span> <span class="operator">=</span> <span class="number">2</span>;</span><br><span class="line">        <span class="type">ExecutorService</span> <span class="variable">executorService</span> <span class="operator">=</span> Executors.newFixedThreadPool(threadNum);</span><br><span class="line">        <span class="type">CountDownLatch</span> <span class="variable">countDownLatch</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">CountDownLatch</span>(threadNum);</span><br><span class="line"></span><br><span class="line">        executorService.execute(() -&gt; &#123;</span><br><span class="line"></span><br><span class="line">            System.out.println(<span class="string">&quot;小明开始出发&quot;</span>);</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                TimeUnit.SECONDS.sleep(<span class="number">2</span>);</span><br><span class="line">            &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">                e.printStackTrace();</span><br><span class="line">            &#125;</span><br><span class="line">            countDownLatch.countDown();  <span class="comment">// 计数器 -1</span></span><br><span class="line">            System.out.println(<span class="string">&quot;小明到达人民广场&quot;</span>);</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">        &#125;);</span><br><span class="line">        executorService.execute(() -&gt; &#123;</span><br><span class="line"></span><br><span class="line">            System.out.println(<span class="string">&quot;小红开始出发&quot;</span>);</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                TimeUnit.SECONDS.sleep(<span class="number">3</span>);</span><br><span class="line">            &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">                e.printStackTrace();</span><br><span class="line">            &#125;</span><br><span class="line">            countDownLatch.countDown();  <span class="comment">// 计数器 -1</span></span><br><span class="line">            System.out.println(<span class="string">&quot;小红到达人民广场&quot;</span>);</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">        &#125;);</span><br><span class="line">        countDownLatch.await();</span><br><span class="line">        System.out.println(<span class="string">&quot;小明和小红都到达了人民广场，开始一起出发去爬山&quot;</span>);</span><br><span class="line">        executorService.shutdown();</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>结果</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">小明开始出发</span><br><span class="line">小红开始出发</span><br><span class="line">小明到达人民广场  <span class="comment">// 2s后打印</span></span><br><span class="line">小红到达人民广场  <span class="comment">// 3s后打印</span></span><br><span class="line">小明和小红都到达了人民广场，开始一起出发去爬山 <span class="comment">//3s后打印</span></span><br><span class="line"></span><br><span class="line">Process finished with exit code <span class="number">0</span></span><br></pre></td></tr></table></figure><h3 id="与Join-的区别"><a href="#与Join-的区别" class="headerlink" title="与Join()的区别"></a>与Join()的区别</h3><p>可能这里会有疑问，使用<code>Thread.join()</code>也可以实现相同的功能，这与使用<code>CountDownLatch</code>有什么区别呢？</p><h4 id="join-的实现"><a href="#join-的实现" class="headerlink" title="join()的实现"></a>join()的实现</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> InterruptedException &#123;</span><br><span class="line">    <span class="type">Thread</span> <span class="variable">thread1</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(() -&gt; &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;小明开始出发&quot;</span>);</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            TimeUnit.SECONDS.sleep(<span class="number">2</span>);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">        System.out.println(<span class="string">&quot;小明到达人民广场&quot;</span>);</span><br><span class="line"></span><br><span class="line">    &#125;, <span class="string">&quot;thread1&quot;</span>);</span><br><span class="line">    <span class="type">Thread</span> <span class="variable">thread2</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(() -&gt; &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;小红开始出发&quot;</span>);</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            TimeUnit.SECONDS.sleep(<span class="number">3</span>);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">        System.out.println(<span class="string">&quot;小红到达人民广场&quot;</span>);</span><br><span class="line"></span><br><span class="line">    &#125;, <span class="string">&quot;thread2&quot;</span>);</span><br><span class="line"></span><br><span class="line">    thread1.start();</span><br><span class="line">    thread2.start();</span><br><span class="line">    thread1.join();</span><br><span class="line">    thread2.join();</span><br><span class="line">    System.out.println(<span class="string">&quot;小明和小红都到达了人民广场，开始一起出发去爬山&quot;</span>);</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>结果</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">小明开始出发</span><br><span class="line">小红开始出发</span><br><span class="line">小明到达人民广场  <span class="comment">// 2s后打印</span></span><br><span class="line">小红到达人民广场  <span class="comment">// 3s后打印</span></span><br><span class="line">小明和小红都到达了人民广场，开始一起出发去爬山 <span class="comment">//3s后打印</span></span><br><span class="line"></span><br><span class="line">Process finished with exit code <span class="number">0</span></span><br></pre></td></tr></table></figure><p>发现使用join()实现和<code>countDownCatch</code>实现好像在代码上的体现并没有太大差异，不急，我们接着往下看</p><h4 id="join-实现原理"><a href="#join-实现原理" class="headerlink" title="join()实现原理"></a>join()实现原理</h4><p>我们点进去join的<code>jdk</code>源码查看它的实现逻辑</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title function_">join</span><span class="params">()</span> <span class="keyword">throws</span> InterruptedException &#123;</span><br><span class="line">    join(<span class="number">0</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title function_">join</span><span class="params">(<span class="type">long</span> millis)</span></span><br><span class="line">    <span class="keyword">throws</span> InterruptedException &#123;</span><br><span class="line">        <span class="type">long</span> <span class="variable">base</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line">        <span class="type">long</span> <span class="variable">now</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (millis &lt; <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(<span class="string">&quot;timeout value is negative&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line"><span class="comment">// 调用join真正执行的方法</span></span><br><span class="line">        <span class="keyword">if</span> (millis == <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="keyword">while</span> (isAlive()) &#123;</span><br><span class="line">                wait(<span class="number">0</span>);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">while</span> (isAlive()) &#123;</span><br><span class="line">                <span class="type">long</span> <span class="variable">delay</span> <span class="operator">=</span> millis - now;</span><br><span class="line">                <span class="keyword">if</span> (delay &lt;= <span class="number">0</span>) &#123;</span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">                &#125;</span><br><span class="line">                wait(delay);</span><br><span class="line">                now = System.currentTimeMillis() - base;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>我们看到他的核心代码就几行</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">while</span> (isAlive()) &#123;</span><br><span class="line">    wait(<span class="number">0</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这几行代码不难理解，通过不停的检查join线程是否存活，如果线程状态是活动的，那么就一直等待下去（wait(0)表示永久等待），直到join线程中止后，线程的<code>this.notifyAll()</code>方法会被调用，不过调用<code>notifyAll()</code>方法是在JVM里 实现的，所以在JDK里看不到。</p><h4 id="Join-与countDownLatch比较"><a href="#Join-与countDownLatch比较" class="headerlink" title="Join()与countDownLatch比较"></a>Join()与countDownLatch比较</h4><p>回到上一个问题，join到底和<code>countDownLatch</code>有什么区别，<code>countDownLatch</code>底层使用了计数器来控制线程的唤醒，提供了更细粒度的线程控制，比如我们运行了100个线程，但是只需要80个线程执行结束就可以继续下去，那么使用join就不合适了。</p><p>综上所述 <code>CountDownLatch</code>相对于Join的优势：</p><ul><li><code>CountDownLatch</code>可以等待多个线程的完成，而Join只能等待一个线程。</li><li><code>CountDownLatch</code>可以灵活地设置计数器的值，不仅仅限于线程数，可以根据需要自由控制。</li><li><code>CountDownLatch</code>提供了更细粒度的线程间协作和控制，可以在任意位置进行<code>countDown</code>()和await()的调用，更灵活地控制线程的流程。</li></ul><h3 id="CountDownLatch程序实现"><a href="#CountDownLatch程序实现" class="headerlink" title="CountDownLatch程序实现"></a>CountDownLatch程序实现</h3><p>上面说了很多<code>CountDownLatch</code>的示例和与join比较，也提了一下<code>CountDownLatch</code>底层的原理，下面就看一下如何实现一个简单的<code>CountDownLatch</code></p><h4 id="程序"><a href="#程序" class="headerlink" title="程序"></a>程序</h4><p>我们先新建一个抽象类，包含countDownLatch需要的参数和方法</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title class_">Latch</span> &#123;</span><br><span class="line">    <span class="comment">// 控制了多少线程完成后门阀才能打开</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="type">int</span> limit;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 构造函数</span></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">Latch</span><span class="params">(<span class="type">int</span> limit)</span>&#123;</span><br><span class="line">        <span class="built_in">this</span>.limit = limit;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 方法使得线程一直等待</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">void</span> <span class="title function_">await</span><span class="params">()</span> <span class="keyword">throws</span> InterruptedException;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 当前任务线程完成工作之后调用该方法使得计数器减一</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">void</span> <span class="title function_">countDown</span><span class="params">()</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获取当前还有多少个线程没有完成任务</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">abstract</span> <span class="type">int</span> <span class="title function_">getUnArrived</span><span class="params">()</span>;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>然后实现这个抽象类，并写入具体逻辑代码</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CountDownLatch</span> <span class="keyword">extends</span> <span class="title class_">Latch</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">Lock</span> <span class="variable">lock</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ReentrantLock</span>();</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">Condition</span> <span class="variable">condition</span> <span class="operator">=</span> lock.newCondition();</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">CountDownLatch</span><span class="params">(<span class="type">int</span> limit)</span> &#123;</span><br><span class="line">        <span class="built_in">super</span>(limit);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">await</span><span class="params">()</span> <span class="keyword">throws</span> InterruptedException &#123;</span><br><span class="line">        lock.lock();</span><br><span class="line">        <span class="keyword">while</span> (limit &gt; <span class="number">0</span>)&#123;</span><br><span class="line">            condition.await();</span><br><span class="line">        &#125;</span><br><span class="line">        lock.unlock();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">countDown</span><span class="params">()</span> &#123;</span><br><span class="line"></span><br><span class="line">        lock.lock();</span><br><span class="line">        <span class="keyword">if</span>(limit &lt; <span class="number">0</span>)&#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>();</span><br><span class="line">        &#125;</span><br><span class="line">        limit--;</span><br><span class="line">        condition.signalAll();</span><br><span class="line"></span><br><span class="line">        lock.unlock();</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">getUnArrived</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> limit;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="测试"><a href="#测试" class="headerlink" title="测试"></a>测试</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">LatchDemo</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> InterruptedException &#123;</span><br><span class="line">        <span class="type">Latch</span> <span class="variable">latch</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">CountDownLatch</span>(<span class="number">2</span>);</span><br><span class="line"></span><br><span class="line">        <span class="type">ExecutorService</span> <span class="variable">executorService</span> <span class="operator">=</span> Executors.newFixedThreadPool(<span class="number">2</span>);</span><br><span class="line"></span><br><span class="line">        executorService.execute(()-&gt;&#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;小明开始出发&quot;</span>);</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                TimeUnit.SECONDS.sleep(<span class="number">2</span>);</span><br><span class="line">            &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">                e.printStackTrace();</span><br><span class="line">            &#125;</span><br><span class="line">            latch.countDown();  <span class="comment">// 计数器 -1</span></span><br><span class="line">            System.out.println(<span class="string">&quot;小明到达人民广场&quot;</span>);</span><br><span class="line">        &#125;);</span><br><span class="line">        executorService.execute(()-&gt;&#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;小红开始出发&quot;</span>);</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                TimeUnit.SECONDS.sleep(<span class="number">3</span>);</span><br><span class="line">            &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">                e.printStackTrace();</span><br><span class="line">            &#125;</span><br><span class="line">            latch.countDown();  <span class="comment">// 计数器 -1</span></span><br><span class="line">            System.out.println(<span class="string">&quot;小红到达人民广场&quot;</span>);</span><br><span class="line">        &#125;);</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">        latch.await();</span><br><span class="line">        System.out.println(<span class="string">&quot;小明和小红都到达了人民广场，开始一起出发去爬山&quot;</span>);</span><br><span class="line"></span><br><span class="line">        executorService.shutdown();</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>结果</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">小明开始出发</span><br><span class="line">小红开始出发</span><br><span class="line">小明到达人民广场</span><br><span class="line">小红到达人民广场</span><br><span class="line">小明和小红都到达了人民广场，开始一起出发去爬山</span><br><span class="line"></span><br><span class="line">Process finished with exit code <span class="number">0</span></span><br></pre></td></tr></table></figure><p>可以看到结果如前文一致，这就实现了一个简单的<code>CountDownLatch</code>,当然具体实现还有更多的细节，如有需要，请翻阅源码。</p><h4 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h4><p>通过上面的简单实现，我们可以看到<code>CountDownLatch</code>基于计数器实现了多线程之间的门阀拦截，底层还是通过线程之间的通讯、锁和计数器控制。</p><h2 id="CyclicBarrier"><a href="#CyclicBarrier" class="headerlink" title="CyclicBarrier"></a>CyclicBarrier</h2><p>除了使用<code>CountDownLatch</code>来实现多线程之间的阻塞同步,也可以使用<code>CyclicBarrier</code>来实现，并且<code>CyclicBarrier</code>提供了比<code>CountDownLatch</code>更强大的功能。</p><p><code>CyclicBarrier</code>的字面意思是可循环使用的屏障。它提供了一种同步机制，使一组线程能够在达到屏障时被阻塞，直到最后一个线程到达屏障时，屏障才会开启，所有被阻塞的线程才能继续执行。</p><p><em>网上找的一张示意图</em></p><p><img src="https://pic.imgdb.cn/item/648c20e51ddac507cc518a1f.png"></p><h3 id="示例-1"><a href="#示例-1" class="headerlink" title="示例"></a>示例</h3><p>还是用之前的例子，模拟小明和小红去爬山，代码如下，结果就不赘述了。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> InterruptedException, BrokenBarrierException &#123;</span><br><span class="line"></span><br><span class="line">    <span class="type">CyclicBarrier</span> <span class="variable">cyclicBarrier</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">CyclicBarrier</span>(<span class="number">3</span>);</span><br><span class="line"></span><br><span class="line">    <span class="type">ExecutorService</span> <span class="variable">executorService</span> <span class="operator">=</span> Executors.newFixedThreadPool(<span class="number">2</span>);</span><br><span class="line"></span><br><span class="line">    executorService.execute(()-&gt;&#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;小明开始出发&quot;</span>);</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            TimeUnit.SECONDS.sleep(<span class="number">2</span>);</span><br><span class="line">            System.out.println(<span class="string">&quot;小明到达人民广场&quot;</span>);</span><br><span class="line">            cyclicBarrier.await(); <span class="comment">// 计数器 -1</span></span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    &#125;);</span><br><span class="line">    executorService.execute(()-&gt;&#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;小红开始出发&quot;</span>);</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            TimeUnit.SECONDS.sleep(<span class="number">3</span>);</span><br><span class="line">            System.out.println(<span class="string">&quot;小红到达人民广场&quot;</span>);</span><br><span class="line">            cyclicBarrier.await(); <span class="comment">// 计数器 -1</span></span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">    cyclicBarrier.await();</span><br><span class="line">    System.out.println(<span class="string">&quot;小明和小红都到达了人民广场，开始一起出发去爬山&quot;</span>);</span><br><span class="line"></span><br><span class="line">    executorService.shutdown();</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>不同的是，这里我设置了三个屏障点 <code>cyclicBarrier.await()</code>;，而使用<code>CountDownLatch</code>只用了两个计数器减一操作 + 一个wait()方法，使用起来很相似，我们说<code>cyclicBarrier</code> 比 <code>CountDownLatch</code> 功能更强大，那么强大在哪里呢？</p><h3 id="重置计数器和获取状态"><a href="#重置计数器和获取状态" class="headerlink" title="重置计数器和获取状态"></a>重置计数器和获取状态</h3><h4 id="重置计数器"><a href="#重置计数器" class="headerlink" title="重置计数器"></a>重置计数器</h4><p><code>CountDownLatch</code>的计数器只能使用一次，而<code>CyclicBarrier</code>的计数器可以使用<code>reset()</code>方法重置。所以<code>CyclicBarrier</code>能处理更为复杂的业务场景。例如，如果计算发生错误，可以重置计数器，并让线程重新执行一次。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> InterruptedException, BrokenBarrierException &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">final</span> <span class="type">int</span> <span class="variable">threadNum</span> <span class="operator">=</span> <span class="number">3</span>;</span><br><span class="line">    <span class="type">ExecutorService</span> <span class="variable">executorService</span> <span class="operator">=</span> Executors.newFixedThreadPool(threadNum);</span><br><span class="line">    <span class="type">CyclicBarrier</span> <span class="variable">cyclicBarrier</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">CyclicBarrier</span>(threadNum, () -&gt; &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;所有线程都到达屏障&quot;</span>);</span><br><span class="line">    &#125;);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; threadNum; i++) &#123;</span><br><span class="line">        executorService.execute(() -&gt; &#123;</span><br><span class="line">            System.out.println(Thread.currentThread().getName() + <span class="string">&quot; 到达屏障&quot;</span>);</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                cyclicBarrier.await();</span><br><span class="line">            &#125; <span class="keyword">catch</span> (InterruptedException | BrokenBarrierException e) &#123;</span><br><span class="line">                e.printStackTrace();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    Thread.sleep(<span class="number">2000</span>); <span class="comment">// 等待一段时间，确保所有线程都到达屏障</span></span><br><span class="line"></span><br><span class="line">    cyclicBarrier.reset(); <span class="comment">// 重置屏障</span></span><br><span class="line"></span><br><span class="line">    System.out.println(<span class="string">&quot;屏障已重置&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; threadNum-<span class="number">1</span>; i++) &#123;</span><br><span class="line">        executorService.execute(() -&gt; &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                TimeUnit.SECONDS.sleep(<span class="number">1</span>);</span><br><span class="line">            &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">                e.printStackTrace();</span><br><span class="line">            &#125;</span><br><span class="line">            System.out.println(Thread.currentThread().getName() + <span class="string">&quot; 到达屏障&quot;</span>);</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                cyclicBarrier.await();</span><br><span class="line">            &#125; <span class="keyword">catch</span> (InterruptedException | BrokenBarrierException e) &#123;</span><br><span class="line">                e.printStackTrace();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    System.out.println(<span class="string">&quot;第二次进入 循环屏障&quot;</span>);</span><br><span class="line">    cyclicBarrier.await();</span><br><span class="line">    System.out.println(<span class="string">&quot;第二次循环  迈过屏障&quot;</span>);</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    executorService.shutdown();</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>结果</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">pool-<span class="number">1</span>-thread-<span class="number">2</span> 到达屏障</span><br><span class="line">pool-<span class="number">1</span>-thread-<span class="number">3</span> 到达屏障</span><br><span class="line">pool-<span class="number">1</span>-thread-<span class="number">1</span> 到达屏障</span><br><span class="line">所有线程都到达屏障</span><br><span class="line">屏障已重置</span><br><span class="line">第二次进入 循环屏障</span><br><span class="line">pool-<span class="number">1</span>-thread-<span class="number">2</span> 到达屏障</span><br><span class="line">pool-<span class="number">1</span>-thread-<span class="number">1</span> 到达屏障</span><br><span class="line">所有线程都到达屏障</span><br><span class="line">第二次循环  迈过屏障</span><br><span class="line"></span><br><span class="line">Process finished with exit code <span class="number">0</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>先说一下<code>CyclicBarrier</code>提供的另一个构造函数<code>CyclicBarrier（int parties，Runnable barrierAction）</code>，用于在线程到达屏障时，优先执行<code>barrierAction</code>，也就是上方代码中用到的这几段</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">CyclicBarrier</span> <span class="variable">cyclicBarrier</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">CyclicBarrier</span>(threadNum, () -&gt; &#123;</span><br><span class="line">    System.out.println(<span class="string">&quot;所有线程都到达屏障&quot;</span>);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>这里用于提示所有的线程到达屏障。紧接着是比较常规的代码，循环构造线程并在线程中执行了 <code>cyclicBarrier.await();</code>到达屏障。重点是 <code>cyclicBarrier.reset(); </code>重置屏障后，我留下一个屏障给主线程测试使用，而在新构造的线程中停留1s, <code>System.out.println(&quot;第二次循环  迈过屏障&quot;);</code>打印在            <code>System.out.println(Thread.currentThread().getName() + &quot; 到达屏障&quot;);</code>之后，说明屏障计数器已经重置并且生效了。</p><h4 id="获取状态"><a href="#获取状态" class="headerlink" title="获取状态"></a>获取状态</h4><p>除了上述的基本功能外，<code>CyclicBarrier</code>也提供了以下API用来查看状态，</p><ul><li><code>getNumberWaiting</code>()   &#x2F;&#x2F; 顾名思义，获取目前正在屏障处阻塞等待的线程数量。</li><li><code>getParties</code>()               &#x2F;&#x2F;  获取屏障数量 也就是我们传入构造函数中的<code>parties</code>参数</li><li><code>isBroken</code>()                   &#x2F;&#x2F;  查询阻塞的线程是否被中断</li></ul><h2 id="参考资源"><a href="#参考资源" class="headerlink" title="参考资源"></a>参考资源</h2><p>《Java并发编程的艺术》</p><p>《Java高并发编程详解》</p><p><code>jdk1.8 api</code>文档</p>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;\assets\css\APlayer.min.css&quot;&gt;&lt;script src=&quot;\assets\js\APlayer.min.js&quot; cla</summary>
      
    
    
    
    <category term="Java" scheme="https://nerkeler.github.io/categories/Java/"/>
    
    
    <category term="JUC" scheme="https://nerkeler.github.io/tags/JUC/"/>
    
    <category term="并发编程" scheme="https://nerkeler.github.io/tags/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/"/>
    
  </entry>
  
  <entry>
    <title>HashMap为什么线程不安全</title>
    <link href="https://nerkeler.github.io/2023/06/16/HashMap%E4%B8%BA%E4%BB%80%E4%B9%88%E7%BA%BF%E7%A8%8B%E4%B8%8D%E5%AE%89%E5%85%A8/"/>
    <id>https://nerkeler.github.io/2023/06/16/HashMap%E4%B8%BA%E4%BB%80%E4%B9%88%E7%BA%BF%E7%A8%8B%E4%B8%8D%E5%AE%89%E5%85%A8/</id>
    <published>2023-06-16T03:24:00.000Z</published>
    <updated>2024-01-04T01:51:03.391Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h1 id="HashMap为什么线程不安全"><a href="#HashMap为什么线程不安全" class="headerlink" title="HashMap为什么线程不安全"></a>HashMap为什么线程不安全</h1><p>转载自：<a href="https://segmentfault.com/a/1190000038989240">java - HashMap为什么线程不安全 - 个人文章 - SegmentFault 思否</a></p><h4 id="一、学习目标"><a href="#一、学习目标" class="headerlink" title="一、学习目标"></a>一、学习目标</h4><p>1、HashMap线程不安全原因：</p><p>原因：</p><ul><li><strong>JDK1.7 中，由于多线程对HashMap进行扩容，调用了HashMap#transfer()，具体原因：某个线程执行过程中，被挂起，其他线程已经完成数据迁移，等CPU资源释放后被挂起的线程重新执行之前的逻辑，数据已经被改变，造成死循环、数据丢失。</strong></li><li><strong>JDK1.8 中，由于多线程对HashMap进行put操作，调用了HashMap#putVal()，具体原因：假设两个线程A、B都在进行put操作，并且hash函数计算出的插入下标是相同的，当线程A执行完第六行代码后由于时间片耗尽导致被挂起，而线程B得到时间片后在该下标处插入了元素，完成了正常的插入，然后线程A获得时间片，由于之前已经进行了hash碰撞的判断，所有此时不会再进行判断，而是直接进行插入，这就导致了线程B插入的数据被线程A覆盖了，从而线程不安全。</strong></li></ul><p>改善：</p><ul><li>数据丢失、死循环已经在在JDK1.8中已经得到了很好的解决，如果你去阅读1.8的源码会发现找不到HashMap#transfer()，因为JDK1.8直接在HashMap#resize()中完成了数据迁移。</li></ul><p>2、HashMap线程不安全的体现：</p><ul><li>JDK1.7 HashMap线程不安全体现在：死循环、数据丢失</li><li>JDK1.8 HashMap线程不安全体现在：数据覆盖</li></ul><h4 id="二、HashMap线程不安全、死循环、数据丢失、数据覆盖的原因"><a href="#二、HashMap线程不安全、死循环、数据丢失、数据覆盖的原因" class="headerlink" title="二、HashMap线程不安全、死循环、数据丢失、数据覆盖的原因"></a>二、HashMap线程不安全、死循环、数据丢失、数据覆盖的原因</h4><p>1、JDK1.7 扩容引发的线程不安全</p><p>HashMap的线程不安全主要是发生在扩容函数中，其中调用了JDK1.7 HashMap #transfer()：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span> <span class="title function_">transfer</span><span class="params">(Entry[] newTable, <span class="type">boolean</span> rehash)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">newCapacity</span> <span class="operator">=</span> newTable.length;</span><br><span class="line">    <span class="keyword">for</span> (Entry&lt;K,V&gt; e : table) &#123;</span><br><span class="line">        <span class="keyword">while</span>(<span class="literal">null</span> != e) &#123;</span><br><span class="line">            Entry&lt;K,V&gt; next = e.next;</span><br><span class="line">            <span class="keyword">if</span> (rehash) &#123;</span><br><span class="line">                e.hash = <span class="literal">null</span> == e.key ? <span class="number">0</span> : hash(e.key);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> indexFor(e.hash, newCapacity);</span><br><span class="line">            e.next = newTable[i];</span><br><span class="line">            newTable[i] = e;</span><br><span class="line">            e = next;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这段代码是HashMap的扩容操作，重新定位每个桶的下标，并采用头插法将元素迁移到新数组中。头插法会将链表的顺序翻转，这也是形成死循环的关键点。理解了头插法后再继续往下看是如何造成死循环以及数据丢失的。</p><p>2、扩容造成死循环和数据丢失</p><p>假设现在有两个线程A、B同时对下面这个HashMap进行扩容操作：</p><p><img src="https://segmentfault.com/img/remote/1460000038989243" alt="image" title="image"></p><p>正常扩容后的结果是下面这样的：</p><p><img src="https://segmentfault.com/img/remote/1460000038989246" alt="image" title="image"></p><p>但是当线程A执行到上面transfer函数的第11行代码时，CPU时间片耗尽，线程A被挂起。即如下图中位置所示：</p><p><img src="https://segmentfault.com/img/remote/1460000038989244" alt="image" title="image"></p><p>此时线程A中：e&#x3D;3、next&#x3D;7、e.next&#x3D;null</p><p><img src="https://segmentfault.com/img/remote/1460000038989245" alt="image" title="image"></p><p>当线程A的时间片耗尽后，CPU开始执行线程B，并在线程B中成功的完成了数据迁移</p><p><img src="https://segmentfault.com/img/remote/1460000038989251" alt="image" title="image"></p><p><strong>重点来了，根据Java内存模式可知，线程B执行完数据迁移后，此时主内存中newTable和table都是最新的，也就是说：7.next&#x3D;3、3.next&#x3D;null。</strong></p><p>随后线程A获得CPU时间片继续执行newTable[i] &#x3D; e，将3放入新数组对应的位置，执行完此轮循环后线程A的情况如下：</p><p><img src="https://segmentfault.com/img/remote/1460000038989250" alt="image" title="image"></p><p>接着继续执行下一轮循环，此时e&#x3D;7，从主内存中读取e.next时发现主内存中7.next&#x3D;3，此时next&#x3D;3，并将7采用头插法的方式放入新数组中，并继续执行完此轮循环，结果如下：</p><p><img src="https://segmentfault.com/img/remote/1460000038989249" alt="image" title="image"></p><p><strong>此时没任何问题。</strong></p><p>上轮next&#x3D;3，e&#x3D;3，执行下一次循环可以发现，3.next&#x3D;null，所以此轮循环将会是最后一轮循环。</p><p>接下来当执行完e.next&#x3D;newTable[i]即3.next&#x3D;7后，3和7之间就相互连接了，当执行完newTable[i]&#x3D;e后，3被头插法重新插入到链表中，执行结果如下图所示：</p><p><img src="https://segmentfault.com/img/remote/1460000038989247" alt="image" title="image"></p><p><strong>上面说了此时e.next&#x3D;null即next&#x3D;null，当执行完e&#x3D;null后，将不会进行下一轮循环。到此线程A、B的扩容操作完成，很明显当线程A执行完后，HashMap中出现了环形结构，当在以后对该HashMap进行操作时会出现死循环。</strong></p><p><strong>并且从上图可以发现，元素5在扩容期间被莫名的丢失了，这就发生了数据丢失的问题。</strong></p><p>3、JDK1.8中的线程不安全</p><p>上面的扩容造成的数据丢失、死循环已经在在JDK1.8中已经得到了很好的解决，如果你去阅读1.8的源码会发现找不到HashMap#transfer()，因为JDK1.8直接在HashMap#resize()中完成了数据迁移。</p><p>为什么说 <strong>JDK1.8会出现数据覆盖的情况？</strong> 我们来看一下下面这段JDK1.8中的put操作代码：</p><p><img src="https://segmentfault.com/img/remote/1460000038989248" alt="image" title="image"></p><p>其中第六行代码是判断是否出现hash碰撞，假设两个线程A、B都在进行put操作，并且hash函数计算出的插入下标是相同的，当线程A执行完第六行代码后由于时间片耗尽导致被挂起，而线程B得到时间片后在该下标处插入了元素，完成了正常的插入，然后线程A获得时间片，由于之前已经进行了hash碰撞的判断，所有此时不会再进行判断，而是直接进行插入，这就导致了线程B插入的数据被线程A覆盖了，从而线程不安全。</p><p>除此之前，还有就是代码的第38行处有个++size，我们这样想，还是线程A、B，这两个线程同时进行put操作时，假设当前HashMap的zise大小为10，当线程A执行到第38行代码时，从主内存中获得size的值为10后准备进行+1操作，但是由于时间片耗尽只好让出CPU，线程B快乐的拿到CPU还是从主内存中拿到size的值10进行+1操作，完成了put操作并将size&#x3D;11写回主内存，然后线程A再次拿到CPU并继续执行(此时size的值仍为10)，当执行完put操作后，还是将size&#x3D;11写回内存，此时，线程A、B都执行了一次put操作，但是size的值只增加了1，所有说还是由于数据覆盖又导致了线程不安全。</p><h4 id="三、如何使HashMap在多线程情况下进行线程安全操作？"><a href="#三、如何使HashMap在多线程情况下进行线程安全操作？" class="headerlink" title="三、如何使HashMap在多线程情况下进行线程安全操作？"></a>三、如何使HashMap在多线程情况下进行线程安全操作？</h4><p>使用 Collections.synchronizedMap(map)，包装成同步Map，原理就是在HashMap的所有方法上synchronized。</p><p>例如：Collections.SynchronizedMap#get()</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> V <span class="title function_">get</span><span class="params">(Object key)</span> &#123;</span><br><span class="line">    <span class="keyword">synchronized</span> (mutex) &#123;</span><br><span class="line">        <span class="keyword">return</span> m.get(key);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="四、总结"><a href="#四、总结" class="headerlink" title="四、总结"></a>四、总结</h4><p>1、HashMap线程不安全原因：</p><p>原因：</p><ul><li>JDK1.7 中，由于多线程对HashMap进行扩容，调用了HashMap#transfer()，具体原因：某个线程执行过程中，被挂起，其他线程已经完成数据迁移，等CPU资源释放后被挂起的线程重新执行之前的逻辑，数据已经被改变，造成死循环、数据丢失。</li><li>JDK1.8 中，由于多线程对HashMap进行put操作，调用了HashMap#putVal()，具体原因：假设两个线程A、B都在进行put操作，并且hash函数计算出的插入下标是相同的，当线程A执行完第六行代码后由于时间片耗尽导致被挂起，而线程B得到时间片后在该下标处插入了元素，完成了正常的插入，然后线程A获得时间片，由于之前已经进行了hash碰撞的判断，所有此时不会再进行判断，而是直接进行插入，这就导致了线程B插入的数据被线程A覆盖了，从而线程不安全。</li></ul><p>改善：</p><ul><li>数据丢失、死循环已经在在JDK1.8中已经得到了很好的解决，如果你去阅读1.8的源码会发现找不到HashMap#transfer()，因为JDK1.8直接在HashMap#resize()中完成了数据迁移。</li></ul><p>2、HashMap线程不安全的体现：</p><ul><li>JDK1.7 HashMap线程不安全体现在：死循环、数据丢失</li><li>JDK1.8 HashMap线程不安全体现在：数据覆盖</li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;\assets\css\APlayer.min.css&quot;&gt;&lt;script src=&quot;\assets\js\APlayer.min.js&quot; cla</summary>
      
    
    
    
    <category term="Java" scheme="https://nerkeler.github.io/categories/Java/"/>
    
    
    <category term="转载" scheme="https://nerkeler.github.io/tags/%E8%BD%AC%E8%BD%BD/"/>
    
    <category term="JUC" scheme="https://nerkeler.github.io/tags/JUC/"/>
    
  </entry>
  
  <entry>
    <title>Java线程池</title>
    <link href="https://nerkeler.github.io/2023/06/11/Java%E7%BA%BF%E7%A8%8B%E6%B1%A0/"/>
    <id>https://nerkeler.github.io/2023/06/11/Java%E7%BA%BF%E7%A8%8B%E6%B1%A0/</id>
    <published>2023-06-11T11:45:00.000Z</published>
    <updated>2024-01-04T01:51:56.002Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h1 id="Java线程池"><a href="#Java线程池" class="headerlink" title="Java线程池"></a>Java线程池</h1><p><em><strong>以下内容为阅读《JAVA并发编程的艺术》学习总结 笔记</strong></em></p><h3 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h3><p>Java中的线程池是运用场景最多的并发框架，几乎所有需要异步或并发执行任务的程序 都可以使用线程池。在开发过程中，合理地使用线程池能够带来3个好处。</p><h4 id="线程池的优点"><a href="#线程池的优点" class="headerlink" title="线程池的优点"></a>线程池的优点</h4><ul><li><strong>降低资源消耗</strong>。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。</li><li><strong>提高响应速度</strong>。当任务到达时，任务可以不需要等待线程创建就能立即执行</li><li><strong>提高线程的可管理性</strong>。线程是稀缺资源，如果无限制的创建，不久会消耗系统资源，还会降低系统的稳定性，使用线程池可以进行统一分配、调优和监控。但是，要做到合理利用线程池，必须对其实现原理了如指掌。</li></ul><h3 id="线程池的实现原理"><a href="#线程池的实现原理" class="headerlink" title="线程池的实现原理"></a>线程池的实现原理</h3><h4 id="线程池的创建"><a href="#线程池的创建" class="headerlink" title="线程池的创建"></a>线程池的创建</h4><p>我们可以通过<code>ThreadPoolExecutor</code>构造方法生成一个线程池对象，<strong>移除了内部代码，只留下了构造参数</strong>，便于理解，代码如下</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="title function_">ThreadPoolExecutor</span><span class="params">(<span class="type">int</span> corePoolSize,</span></span><br><span class="line"><span class="params">                              <span class="type">int</span> maximumPoolSize,</span></span><br><span class="line"><span class="params">                              <span class="type">long</span> keepAliveTime,</span></span><br><span class="line"><span class="params">                              TimeUnit unit,</span></span><br><span class="line"><span class="params">                              BlockingQueue&lt;Runnable&gt; workQueue,</span></span><br><span class="line"><span class="params">                              ThreadFactory threadFactory,</span></span><br><span class="line"><span class="params">                              RejectedExecutionHandler handler)</span> &#123;</span><br><span class="line">       </span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><p>参数对应关系</p><table><thead><tr><th>参数</th><th>含义</th></tr></thead><tbody><tr><td><code>corePoolSize</code></td><td>核心线程数</td></tr><tr><td><code>maximumPoolSize</code></td><td>最大线程数</td></tr><tr><td><code>keepAliveTime</code></td><td>线程活动保持时间</td></tr><tr><td><code>TimeUnit</code></td><td>线程活动保持时间的单位</td></tr><tr><td><code>BlockingQueue</code><Runnable></td><td>任务队列</td></tr><tr><td><code>ThreadFactory</code></td><td>线程工厂</td></tr><tr><td><code>RejectedExecutionHandler</code></td><td>拒绝策略</td></tr></tbody></table><p>大致明白参数意义后，我们说一下线程池新建一个线程的过程</p><p>先看一下线程池的执行示意图</p><ol><li>主线程执行<code>execute</code>()提交一个新线程，首先进入核心线程池<code>corePoolSize</code>并判断是否已满，如果未满则新建一个线程<strong>（注意，执行这一步骤 需要获取全局锁）。</strong></li><li>如果核心线程池已满，则进入阻塞队列<code>BlockingQueue</code>进行判断是否已满，如果未满则插入队列（队列保持先进先出的顺序执行 ）</li><li>如果阻塞队列已满，则判断线程池是否达到最大线程数量<code>maximumPoolSize</code>，如果没有，则新建线程<strong>（注意，执行这一步骤 需要获取全局锁）。</strong></li><li>如果达到最大线程数量，则执行拒绝策略<code>RejectedExecutionHandler</code></li></ol><p><img src="https://pic.imgdb.cn/item/64807dd91ddac507cc2c89a9.png"></p><p>如果将上述描述转化成流程图则为</p><p><img src="https://pic.imgdb.cn/item/64807fbf1ddac507cc2f94cf.png"></p><h4 id="源码分析"><a href="#源码分析" class="headerlink" title="源码分析"></a>源码分析</h4><p>上面的流程分析让我们很直观地了解了线程池的工作原理，让我们再通过源代 码来看看是如何实现的，线程池执行任务的方法如下。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">execute</span><span class="params">(Runnable command)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (command == <span class="literal">null</span>)</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">NullPointerException</span>();</span><br><span class="line"></span><br><span class="line">    <span class="type">int</span> <span class="variable">c</span> <span class="operator">=</span> ctl.get();</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 是否小于核心线程数，小于执行当前线程</span></span><br><span class="line">    <span class="keyword">if</span> (workerCountOf(c) &lt; corePoolSize) &#123;</span><br><span class="line">        <span class="keyword">if</span> (addWorker(command, <span class="literal">true</span>))</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        c = ctl.get();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 否则尝试加入阻塞队列中</span></span><br><span class="line">    <span class="keyword">if</span> (isRunning(c) &amp;&amp; workQueue.offer(command)) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">recheck</span> <span class="operator">=</span> ctl.get();</span><br><span class="line">        <span class="keyword">if</span> (! isRunning(recheck) &amp;&amp; remove(command))</span><br><span class="line">            reject(command);</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (workerCountOf(recheck) == <span class="number">0</span>)</span><br><span class="line">            addWorker(<span class="literal">null</span>, <span class="literal">false</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 最后尝试加入最大线程中</span></span><br><span class="line">    <span class="keyword">else</span> <span class="keyword">if</span> (!addWorker(command, <span class="literal">false</span>))</span><br><span class="line">        reject(command);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="工作线程"><a href="#工作线程" class="headerlink" title="工作线程"></a>工作线程</h4><p>线程池创建线程时，会将线程封装成工作线程Worker，Worker在执行完任务后，还会循环获取工作队列中的任务来执行，我们可以从Worker类的run()方法里看到这点。</p><p><em><strong>只关注while循环和内部<code>task.run</code>即可</strong></em></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">    runWorker(<span class="built_in">this</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">final</span> <span class="keyword">void</span> <span class="title function_">runWorker</span><span class="params">(Worker w)</span> &#123;</span><br><span class="line">    <span class="type">Thread</span> <span class="variable">wt</span> <span class="operator">=</span> Thread.currentThread();</span><br><span class="line">    <span class="type">Runnable</span> <span class="variable">task</span> <span class="operator">=</span> w.firstTask;</span><br><span class="line">    w.firstTask = <span class="literal">null</span>;</span><br><span class="line">    w.unlock(); <span class="comment">// allow interrupts</span></span><br><span class="line">    <span class="type">boolean</span> <span class="variable">completedAbruptly</span> <span class="operator">=</span> <span class="literal">true</span>;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">while</span> (task != <span class="literal">null</span> || (task = getTask()) != <span class="literal">null</span>) &#123;</span><br><span class="line">            w.lock();</span><br><span class="line">            <span class="comment">// 二次验证线程是否中断或停止</span></span><br><span class="line">            <span class="keyword">if</span> ((runStateAtLeast(ctl.get(), STOP) ||</span><br><span class="line">                 (Thread.interrupted() &amp;&amp;</span><br><span class="line">                  runStateAtLeast(ctl.get(), STOP))) &amp;&amp;</span><br><span class="line">                !wt.isInterrupted())</span><br><span class="line">                wt.interrupt();</span><br><span class="line">            </span><br><span class="line">            <span class="comment">// 线程执行</span></span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                beforeExecute(wt, task);</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    task.run();</span><br><span class="line">                    afterExecute(task, <span class="literal">null</span>);</span><br><span class="line">                &#125; <span class="keyword">catch</span> (Throwable ex) &#123;</span><br><span class="line">                    afterExecute(task, ex);</span><br><span class="line">                    <span class="keyword">throw</span> ex;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                task = <span class="literal">null</span>;</span><br><span class="line">                w.completedTasks++;</span><br><span class="line">                w.unlock();</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            </span><br><span class="line">        &#125;</span><br><span class="line">        completedAbruptly = <span class="literal">false</span>;</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        processWorkerExit(w, completedAbruptly);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>ThreadPoolExecutor</code>中线程执行任务的示意图如下</p><img src="https://pic.imgdb.cn/item/6481703c1ddac507cc81ba88.png"><p><strong>线程池中得先成功执行任务份两种情况</strong></p><ul><li>在execute()方法中创建一个线程时，会让这个线程执行当前任务</li><li>这个线程执行完上图中1得任务后，会反复从<code>BlockingQueue</code>获取任务来执行</li></ul><h4 id="线程池参数"><a href="#线程池参数" class="headerlink" title="线程池参数"></a>线程池参数</h4><h5 id="corePoolSize（核心线程数）"><a href="#corePoolSize（核心线程数）" class="headerlink" title="corePoolSize（核心线程数）"></a><code>corePoolSize</code>（核心线程数）</h5><p>当提交一个任务到线程池中时，线程池会创建一个线程来执行任务，即使其他空闲的基本线程能够执行新任务也会创建线程，等待需要执行的任务数大于线程池基本大小（核心线程数）时就不在创建。如果调用了线程池的prestartAllCoreThreads()方法，线程池会提前创建并启动所有核心线程。</p><h5 id="runnableTaskQueue（任务队列）"><a href="#runnableTaskQueue（任务队列）" class="headerlink" title="runnableTaskQueue（任务队列）"></a><code>runnableTaskQueue</code>（任务队列）</h5><p>用于保存等待执行的任务的阻塞队列。有以下几种</p><ul><li><code>ArrayBlockingQueue</code>: 一个基于数组结构的有界阻塞队列，按照FIFO（first in first out)先进先出的原则对元素进行排序。</li><li><code>LinkedBlockingQueue</code>:一个基于链表结构的阻塞队列，也是按照FIFO 原则排序元素，吞吐量通常高于<code>ArrayBlockingQueue</code>。静态工厂方法<code>Executors.newFixedThreadPool()</code>使用了这个队列。</li><li><code>SynchronousQueue</code>: 一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作，否则插入操作一直处于阻塞状态，吞吐量通常要高于<code>LinkedBlockingQueue</code>,静态工厂方法<code>Executors.newCachedThreadPool</code>使用了这个队列</li><li><code>ProirityBlockingQueue</code>:一个具有优先级的无限阻塞队列</li></ul><h5 id="maximumPoolSize（最大线程数）"><a href="#maximumPoolSize（最大线程数）" class="headerlink" title="maximumPoolSize（最大线程数）"></a><code>maximumPoolSize</code>（最大线程数）</h5><p>线程池允许创建的最大线程数。如果核心线程和队列都已经满了，并且已创建的线程数小于最大线程数，也就是上图中3这种情况，后续提交的线程则会创建新线程，<strong>当使用无界的任务队列时，提交的线程会无限阻塞在任务队列中，这个参数就失去了意义。</strong></p><h5 id="ThreadFactory（线程工厂）"><a href="#ThreadFactory（线程工厂）" class="headerlink" title="ThreadFactory（线程工厂）"></a><code>ThreadFactory</code>（线程工厂）</h5><p><code>ThreadFactory</code> 是一个接口，用于创建线程的工厂。它提供了一种创建和定制线程的方式，让你能够对线程进行更多的控制和管理。例如自定义线程名称、设置线程优先级、设置线程组等。</p><h5 id="RejectedExecutionHandler（拒绝策略）"><a href="#RejectedExecutionHandler（拒绝策略）" class="headerlink" title="RejectedExecutionHandler（拒绝策略）"></a><code>RejectedExecutionHandler</code>（拒绝策略）</h5><p>当队列和线程池都满了，说明明线程池处于饱和状态，那么必须才需一种策略处理提交的新任务。默认请情况下使用的时AbortPolicy,表示无法处理新任务时抛出异常。自JDK1.5 Java线程池提供了4中策略</p><ul><li><code>AbortPolicy</code> 直接抛出异常</li><li><code>CallerRunsPolicy</code> 只用调用者所在的线程运行任务</li><li><code>DiscardOldestPolicy</code> 丢弃队列里最旧的任务也就是最早添加的一个任务，并尝试执行当前任务</li><li><code>DiscardPolicy</code> 不处理  直接丢弃</li></ul><p>除此之外，我们可以根据业务场景自己实现<code>RejectedExecutionHandler</code>接口自定义策略，比如记录日志或持久化不能处理的任务</p><h5 id="keepAliveTime（存活时间）"><a href="#keepAliveTime（存活时间）" class="headerlink" title="keepAliveTime（存活时间）"></a><code>keepAliveTime</code>（存活时间）</h5><p>线程池对的工作线程空闲后，保持存活的时间，线程在存活时间内可被重复利用，如果我们执行任务的时间比较短，调大存活时间可以提高线程利用率，避免线程的重新启动和终止所产生的额外时间消耗</p><h5 id="TimeUnit-存活时间单位"><a href="#TimeUnit-存活时间单位" class="headerlink" title="TimeUnit(存活时间单位)"></a><code>TimeUnit</code>(存活时间单位)</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">java.util.concurrent.TimeUnit</span><br></pre></td></tr></table></figure><p>可选</p><ul><li>SECONDS                           秒</li><li>DAYS                                   天</li><li>HOURS                               小时</li><li>MICROSECONDS              微秒</li><li>MILLISECONDS                 毫秒</li><li>MINUTES                           分钟</li><li>NANOSECONDS               纳秒</li></ul><h4 id="提交线程任务"><a href="#提交线程任务" class="headerlink" title="提交线程任务"></a>提交线程任务</h4><p>线程池提交任务使用execute()方法和submit()方法，区别在于execute()方法无返回值，submit()方法可以获取线程执行返回值，它返回一个Future接口对象，可以使用<code>future.get()</code>阻塞线程获取返回值</p><h5 id="execute"><a href="#execute" class="headerlink" title="execute()"></a>execute()</h5><p>构造方法</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">execute</span><span class="params">(Runnable command)</span></span><br></pre></td></tr></table></figure><p>可以看到execute()接收一个Runnable的实例对象，也无返回值，所以无法判断任务是否是否被线程池执行成功（如果线程没有打印或日志的情况下），一个小例子如下</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="type">ExecutorService</span> <span class="variable">executorService</span> <span class="operator">=</span> Executors.newFixedThreadPool(<span class="number">3</span>);</span><br><span class="line">    executorService.execute(()-&gt; System.out.println(<span class="string">&quot;提交新线程成功&quot;</span>));</span><br><span class="line">    executorService.shutdown();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>结果不言而喻</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">提交新线程成功</span><br><span class="line"></span><br><span class="line">Process finished with exit code <span class="number">0</span></span><br></pre></td></tr></table></figure><h5 id="submit"><a href="#submit" class="headerlink" title="submit()"></a>submit()</h5><p>构造方法</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Future&lt;?&gt; submit(Runnable task);</span><br><span class="line">&lt;T&gt; Future&lt;T&gt; <span class="title function_">submit</span><span class="params">(Runnable task, T result)</span>;</span><br><span class="line">&lt;T&gt; Future&lt;T&gt; <span class="title function_">submit</span><span class="params">(Callable&lt;T&gt; task)</span>;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>可以看到submit()方法返回一个Future接口对象，我们可以通过Future的get()方法获取返回值，当然get()方法会阻塞当前线程</p><p>一个小例子</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="type">ExecutorService</span> <span class="variable">executorService</span> <span class="operator">=</span> Executors.newFixedThreadPool(<span class="number">3</span>);</span><br><span class="line">    Future&lt;String&gt; submit = executorService.submit(() -&gt; <span class="string">&quot;提交新线程成功&quot;</span>);</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        System.out.println(submit.get());</span><br><span class="line">    &#125; <span class="keyword">catch</span> (InterruptedException | ExecutionException e) &#123;</span><br><span class="line">        e.printStackTrace();</span><br><span class="line">    &#125;</span><br><span class="line">    executorService.shutdown();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>结果</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">提交新线程成功</span><br><span class="line"></span><br><span class="line">Process finished with exit code <span class="number">0</span></span><br></pre></td></tr></table></figure><h4 id="关闭线程池"><a href="#关闭线程池" class="headerlink" title="关闭线程池"></a>关闭线程池</h4><p>关闭线程池可以使用shutdown或shutdownNow方法，他们的原理是遍历线程池的工作线程，然后逐个调用线程的interrupt方法来中断线程。</p><p>区别在于shutdownNow会先将线程池的状态设置成stop,然后尝试停止所有正在执行或暂停的任务线程，并返回等待执行任务的列表，而shutdown只是将线程池的状态设置成SHUTDOWN状态，然后中断所有没有正在执行的任务线程</p><p>只要调用这两个方法中的任意一个，<code>isShutdown</code>方法就会返回<code>ture</code>,当所有任务都已经关闭后，才表示线程池关闭成功，这个时候调用<code>idTerminaed</code>方法会返回<code>true</code>。通常使用shutdown方法关闭线程。</p><h4 id="合理地配置线程池"><a href="#合理地配置线程池" class="headerlink" title="合理地配置线程池"></a>合理地配置线程池</h4><p>要想合理地配置线程池，就必须首先分析任务特性，可以从以下几个角度来分析。</p><ul><li>任务的性质：CPU密集型任务、IO密集型任务和混合型任务。 </li><li>任务的优先级：高、中和低。 ·任务的执行时间：长、中和短。</li><li>任务的依赖性：是否依赖其他系统资源，如数据库连接。</li></ul><p>性质不同的任务可以用不同规模的线程池分开处理。CPU密集型任务应配置尽可能小的 线程，如配置<code>**Ncpu+1**</code>个线程的线程池。由于IO密集型任务线程并不是一直在执行任务，则应配 置尽可能多的线程，如<code>**2*Ncpu**</code>。混合型的任务，如果可以拆分，将其拆分成一个CPU密集型任务 和一个IO密集型任务，只要这两个任务执行的时间相差不是太大，那么分解后执行的吞吐量 将高于串行执行的吞吐量。如果这两个任务执行时间相差太大，则没必要进行分解。可以通过 <code>**Runtime.getRuntime().availableProcessors()**</code>方法获得当前设备的CPU个数。</p><ul><li>优先级不同的任务可以使用优先级队列<code>PriorityBlockingQueue</code>来处理。它可以让优先级高 的任务先执行。</li><li>执行时间不同的任务可以交给不同规模的线程池来处理，或者可以使用优先级队列，让 执行时间短的任务先执行。</li><li>依赖数据库连接池的任务，因为线程提交SQL后需要等待数据库返回结果，等待的时间越 长，则CPU空闲时间就越长，那么线程数应该设置得越大，这样才能更好地利用CPU。</li></ul><p><em><strong>PS:</strong></em></p><p>***如果一直有优先级高的任务提交到队列里，那么优先级低的任务可能永远不能 执行。***</p><p>​*<strong>建议使用有界队列</strong>,有界队列能增加系统的稳定性和预警能力。</p><h4 id="线程池的监控"><a href="#线程池的监控" class="headerlink" title="线程池的监控"></a>线程池的监控</h4><p>如果在系统中大量使用线程池，则有必要对线程池进行监控，方便在出现问题时，可以根据线程池的使用状况快速定位问题。</p><p>可以通过线程池提供的参数进行监控，在监控线程池的 时候可以使用以下属性。 </p><ul><li><code>taskCount</code>：线程池需要执行的任务数量。 </li><li><code>completedTaskCount</code>：线程池在运行过程中已完成的任务数量，小于或等于<code>taskCount</code>。 </li><li><code>largestPoolSize</code>：线程池里曾经创建过的最大线程数量。通过这个数据可以知道线程池是 否曾经满过。如该数值等于线程池的最大大小，则表示线程池曾经满过。</li><li><code>getPoolSize</code>：线程池的线程数量。如果线程池不销毁的话，线程池里的线程不会自动销 毁，所以这个大小只增不减。 </li><li><code>getActiveCount</code>：获取活动的线程数。</li></ul><p>通过扩展线程池进行监控。可以通过继承线程池来自定义线程池，重写线程池的 <code>beforeExecute</code>、<code>afterExecute</code>和<code>terminated</code>方法，也可以在任务执行前、执行后和线程池关闭前执 行一些代码来进行监控。例如，监控任务的平均执行时间、最大执行时间和最小执行时间等。 这几个方法在线程池里是空方法。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;\assets\css\APlayer.min.css&quot;&gt;&lt;script src=&quot;\assets\js\APlayer.min.js&quot; cla</summary>
      
    
    
    
    <category term="Java" scheme="https://nerkeler.github.io/categories/Java/"/>
    
    
    <category term="JUC" scheme="https://nerkeler.github.io/tags/JUC/"/>
    
    <category term="线程池" scheme="https://nerkeler.github.io/tags/%E7%BA%BF%E7%A8%8B%E6%B1%A0/"/>
    
  </entry>
  
  <entry>
    <title>Future和CompletableFuture</title>
    <link href="https://nerkeler.github.io/2023/06/04/UntitledFuture%E5%92%8CCompletableFuture/"/>
    <id>https://nerkeler.github.io/2023/06/04/UntitledFuture%E5%92%8CCompletableFuture/</id>
    <published>2023-06-04T03:36:00.000Z</published>
    <updated>2024-01-04T01:53:26.499Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h1 id="Future和CompletableFuture"><a href="#Future和CompletableFuture" class="headerlink" title="Future和CompletableFuture"></a>Future和CompletableFuture</h1><h2 id="FutureTask"><a href="#FutureTask" class="headerlink" title="FutureTask"></a>FutureTask</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">* @since 1.5</span></span><br><span class="line"><span class="comment">* @author Doug Lea</span></span><br><span class="line"><span class="comment">* @param &lt;V&gt; The result type returned by this FutureTask&#x27;s &#123;@code get&#125; methods</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">FutureTask</span>&lt;V&gt; <span class="keyword">implements</span> <span class="title class_">RunnableFuture</span>&lt;V&gt; &#123;</span><br></pre></td></tr></table></figure><p>Future接口在Java 5中被引入，设计初衷是对将来某个时刻会发生的结果进行建模。它建模 了一种异步计算，返回一个执行运算结果的引用，当运算结束后，这个引用被返回给调用方。</p><h4 id="一个简单例子"><a href="#一个简单例子" class="headerlink" title="一个简单例子"></a>一个简单例子</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> InterruptedException, ExecutionException &#123;</span><br><span class="line">       <span class="type">ExecutorService</span> <span class="variable">executor</span> <span class="operator">=</span> Executors.newCachedThreadPool();</span><br><span class="line">       <span class="comment">// 把需要耗时得事情交给 future 中得线程池</span></span><br><span class="line">       Future&lt;Double&gt; future = executor.submit(() -&gt; &#123;</span><br><span class="line">           TimeUnit.SECONDS.sleep(<span class="number">4</span>);</span><br><span class="line">           System.out.println(<span class="string">&quot;doSomeLongComputation&quot;</span>);</span><br><span class="line">           <span class="keyword">return</span> ThreadLocalRandom.current().nextDouble();</span><br><span class="line">       &#125;);</span><br><span class="line">       <span class="comment">// 做一些其他的事情</span></span><br><span class="line">       System.out.println(<span class="string">&quot;doSomethingElse&quot;</span>);</span><br><span class="line">       <span class="keyword">try</span> &#123;</span><br><span class="line">           <span class="type">Double</span> <span class="variable">result</span> <span class="operator">=</span> future.get(<span class="number">1</span>, TimeUnit.SECONDS);</span><br><span class="line">           System.out.println(result);</span><br><span class="line">       &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">           <span class="comment">// 计算抛出一个异常</span></span><br><span class="line">           e.printStackTrace();</span><br><span class="line">       &#125;</span><br><span class="line"></span><br><span class="line">       <span class="comment">// 关闭线程池</span></span><br><span class="line">       executor.shutdown();</span><br><span class="line">   &#125;</span><br></pre></td></tr></table></figure><p>程序结果</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">doSomethingElse</span><br><span class="line">doSomeLongComputation</span><br><span class="line"><span class="number">0.3039716200549002</span></span><br><span class="line"></span><br><span class="line">Process finished with exit code <span class="number">0</span></span><br></pre></td></tr></table></figure><p>上述代码让我们可以以并发得方式调用另一个线程执行耗时操作得同时，也可以去执行一些其他的任务。启动异步任务，后我们可以调用它得get()方法获取操作返回值，如果通过get获取时程序在传入规定时间还没有返回值，则会抛出异常，获取时还没有生成返回值则会进行阻塞。</p><p><img src="https://pic.imgdb.cn/item/6475a5ecf024cca1734dcc0b.png"></p><p>理解了上述执行流程，我们看一下Future接口提供的几个基本方法</p><table><thead><tr><th>Modifier and Type</th><th>Method and Description</th></tr></thead><tbody><tr><td><code>boolean</code></td><td>cancel(<code>boolean</code> <code>mayInterruptIfRunning</code>) 尝试取消执行此任务。</td></tr><tr><td>V</td><td>get() 等待计算完成，然后检索其结果。</td></tr><tr><td>V</td><td>get(long timeout, <code>TimeUnit</code> unit) 如果需要等待最多在给定的时间计算完成，然后检索其结果（如果可用）。</td></tr><tr><td><code>boolean</code></td><td>isCancelled() 如果此任务在正常完成之前被取消，则返回 true 。</td></tr><tr><td><code>boolean</code></td><td>isDone() 返回 true如果任务已完成。</td></tr></tbody></table><p>我们看到get方法有两个重载得方法，get(long timeout,<code>TimeUnit</code> unit) 和get(）的区别是如果在指定时间内没有得到结果的返回值，则会抛出异常，程序会继续运行，而get()方法则会阻塞主线程进行等待，知道获取返回值，如果异步线程是一个极其耗费时间的操作，则会导致主线程进入漫长的等待</p><h4 id="Future-接口的局限性"><a href="#Future-接口的局限性" class="headerlink" title="Future 接口的局限性"></a><strong>Future 接口的局限性</strong></h4><p>通过上述例子可以看到Future接口可能会导致程序进入漫长的等待或者直接抛弃获取值操作，虽然我们可以通过isDone()检测程序是否运行完毕，但是始终还是存在一定的运行阻塞与等待<br>如果我们有这样一个需求：我们需要完成两个计算任务，当前一个计算任务结束后，需要将改计算的结果通知到另一个长时间运行的计算任务，当两个计算任务都结束后，将两个计算任务合并后输出。 新的<code>CompletableFuture</code>类  会很方便的解决这类问题。</p><h2 id="CompletableFuture"><a href="#CompletableFuture" class="headerlink" title="CompletableFuture"></a><strong><code>CompletableFuture</code></strong></h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CompletableFuture</span>&lt;T&gt; <span class="keyword">implements</span> <span class="title class_">Future</span>&lt;T&gt;, CompletionStage&lt;T&gt; </span><br></pre></td></tr></table></figure><p>查看类图可知，<code>CompletableFuture</code> 实现了Future接口和<code>CompletionStage</code>接口，也就是说在原有Future接口的基础上提供了一些扩展，自然功能更为强大</p><p><img src="https://pic.imgdb.cn/item/6476a114f024cca1733367f4.png"></p><h4 id="CompletionStage接口"><a href="#CompletionStage接口" class="headerlink" title="CompletionStage接口"></a><code>CompletionStage</code>接口</h4><p><code>CompletionStage</code> 是 Java 8 引入的接口，用于支持异步计算和处理结果的操作。它是 Java 中的一个 Future 接口的扩展，并提供了更强大的异步编程模型</p><ol><li><code>CompletionStage</code>代表异步计算过程中的某一个阶段,一个阶段完成后可能会触发另外一个阶段</li><li>一个阶段的计算执行可以是一个<code>Funcation</code>、Consumer、Runnable。比如:<code>stage.thenApply (x-&gt;square(x)).thenAccept(x-&gt;System.out.println(x)).thenRun(()-&gt;&#123;System.out.println()&#125;);</code></li><li>一个阶段的执行可能是被单个阶段的完成触发,也可能是由多个阶段一起触发.有些类似Linux系统的管道分隔符传参数</li></ol><h4 id="示例"><a href="#示例" class="headerlink" title="示例"></a>示例</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CompletableFutureExample</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="comment">// 创建一个异步任务</span></span><br><span class="line">        CompletableFuture&lt;String&gt; future = CompletableFuture.supplyAsync(() -&gt; &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                TimeUnit.SECONDS.sleep(<span class="number">2</span>);</span><br><span class="line">            &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">                e.printStackTrace();</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">return</span> <span class="string">&quot;Hello, CompletableFuture!&quot;</span>;</span><br><span class="line">        &#125;);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 注册任务完成后的回调</span></span><br><span class="line">        future.thenAccept(result -&gt; &#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;Task completed: &quot;</span> + result);</span><br><span class="line">        &#125;);</span><br><span class="line"></span><br><span class="line">        System.out.println(<span class="string">&quot;Main thread continues to execute...&quot;</span>);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            TimeUnit.SECONDS.sleep(<span class="number">3</span>);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>结果</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Main thread continues to execute...</span><br><span class="line">Task completed: Hello, CompletableFuture!</span><br><span class="line"></span><br><span class="line">Process finished with exit code <span class="number">0</span></span><br></pre></td></tr></table></figure><p>如上所示<code>CompletableFuture</code>使用静态方法构造<code>CompletableFuture</code>对象，常用的静态构造方法如下所示</p><h4 id="常用静态构造方法"><a href="#常用静态构造方法" class="headerlink" title="常用静态构造方法"></a><strong>常用静态构造方法</strong></h4><table><thead><tr><th>Modifier and Type</th><th>Modifier and Type</th><th>describe</th></tr></thead><tbody><tr><td><code>CompletableFuture</code><Void></td><td><code>runAsync</code>(<code>Runnablerunnable</code>)</td><td>无返回值</td></tr><tr><td><code>CompletableFuture</code><Void></td><td><code>runAsync</code>(<code>Runnablerunnable</code>, <code>Executorexecutor</code>)</td><td>无返回值，指定线程池</td></tr><tr><td><code>CompletableFuture</code><U></td><td>supplyAsync(Supplier<U>supplier)</td><td>有返回值</td></tr><tr><td><code>CompletableFuture</code><U></td><td>supplyAsync(Supplier<U>supplier, Executor executor)</td><td>有返回值，指定线程池</td></tr></tbody></table><p>为什么不用构造方法呢？我们看一下jdk文档的描述</p><h4 id="构造方法摘要"><a href="#构造方法摘要" class="headerlink" title="构造方法摘要"></a>构造方法摘要</h4><table><thead><tr><th>Constructor and Description</th></tr></thead><tbody><tr><td><code>CompletableFuture</code>() 创建一个新的不完整的<code>CompletableFuture</code>。</td></tr></tbody></table><p>默认空参构造器只能新建一个不完整的<code>CompletableFuture</code>对象，原因简略看一下</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Returns a new CompletableFuture that is asynchronously completed</span></span><br><span class="line"><span class="comment"> * by a task running in the &#123;<span class="doctag">@link</span> ForkJoinPool#commonPool()&#125; with</span></span><br><span class="line"><span class="comment"> * the value obtained by calling the given Supplier.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> supplier a function returning the value to be used</span></span><br><span class="line"><span class="comment"> * to complete the returned CompletableFuture</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> &lt;U&gt; the function&#x27;s return type</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> the new CompletableFuture</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> &lt;U&gt; CompletableFuture&lt;U&gt; <span class="title function_">supplyAsync</span><span class="params">(Supplier&lt;U&gt; supplier)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> asyncSupplyStage(asyncPool, supplier);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> &lt;U&gt; CompletableFuture&lt;U&gt; <span class="title function_">asyncSupplyStage</span><span class="params">(Executor e,</span></span><br><span class="line"><span class="params">                                                 Supplier&lt;U&gt; f)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (f == <span class="literal">null</span>) <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">NullPointerException</span>();</span><br><span class="line">    CompletableFuture&lt;U&gt; d = <span class="keyword">new</span> <span class="title class_">CompletableFuture</span>&lt;U&gt;();</span><br><span class="line">    e.execute(<span class="keyword">new</span> <span class="title class_">AsyncSupply</span>&lt;U&gt;(d, f));</span><br><span class="line">    <span class="keyword">return</span> d;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>supplyAsync</code>静态方法在执行时，也new了一个新的<code>ComPletableFuture</code>对象，不同的是，<code>supplyAsync</code>方法调用<code>asyncSupplyStage</code>后使用e.<code>execute</code> 开启了线程。所以<code>ComPletableFuture</code> 空参构造只是一个<code>ComPletableFuture</code>对象，还需要进一步开启线程，其他静态构造方法原理一致。</p><h4 id="线程池相关"><a href="#线程池相关" class="headerlink" title="线程池相关"></a><strong>线程池相关</strong></h4><p>通过上面的构造方法可以看到，相同两个方法名之间的区别就是是否指定了新的线程池，如果没有指定线程池，则会使用<code>ComPletableFuture</code>内置的线程池<code>ForkJoinPool</code>。</p><p>实现源码如下</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> &lt;U&gt; CompletableFuture&lt;U&gt; <span class="title function_">supplyAsync</span><span class="params">(Supplier&lt;U&gt; supplier)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> asyncSupplyStage(asyncPool, supplier);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Default executor -- ForkJoinPool.commonPool() unless it cannot</span></span><br><span class="line"><span class="comment"> * support parallelism.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Executor</span> <span class="variable">asyncPool</span> <span class="operator">=</span> useCommonPool ?</span><br><span class="line">    ForkJoinPool.commonPool() : <span class="keyword">new</span> <span class="title class_">ThreadPerTaskExecutor</span>();</span><br><span class="line"></span><br><span class="line"><span class="comment">/** Fallback if ForkJoinPool.commonPool() cannot support parallelism */</span></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">ThreadPerTaskExecutor</span> <span class="keyword">implements</span> <span class="title class_">Executor</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">execute</span><span class="params">(Runnable r)</span> &#123; <span class="keyword">new</span> <span class="title class_">Thread</span>(r).start(); &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可以看到，这里只是做了一个三目运算来判断是否指定了线程池。</p><h4 id="简单实践"><a href="#简单实践" class="headerlink" title="简单实践"></a>简单实践</h4><p>我们指定一个字符串列表，取出每个字符串并获得第一个字符 ，等待一秒后，与一个随机数相乘, 比如字符串”hello” ,取出h，等待一秒后，用h的ASCII码与一个随机浮点数相乘。</p><h5 id="Stream流方式"><a href="#Stream流方式" class="headerlink" title="Stream流方式"></a>Stream流方式</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    List&lt;String&gt; strings = Arrays.asList(<span class="string">&quot;BestPrice&quot;</span>, <span class="string">&quot;LetsSaveBig&quot;</span>, <span class="string">&quot;MyFavoriteShop&quot;</span>, <span class="string">&quot;BuyItAll&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="type">long</span> <span class="variable">start</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line"></span><br><span class="line">    List&lt;Double&gt; collect = strings.stream()</span><br><span class="line">            .map(s -&gt; &#123;</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    Thread.sleep(<span class="number">1000</span>);</span><br><span class="line">                &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">                    e.printStackTrace();</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">return</span> s.charAt(<span class="number">0</span>) * ThreadLocalRandom.current().nextDouble();</span><br><span class="line"></span><br><span class="line">            &#125;)</span><br><span class="line">            .collect(Collectors.toList());</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="type">long</span> <span class="variable">end</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line">    System.out.println(end - start);</span><br><span class="line">    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>执行时间为： <strong>4108毫秒</strong></p><h5 id="并行流方式"><a href="#并行流方式" class="headerlink" title="并行流方式"></a>并行流方式</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    List&lt;String&gt; strings = Arrays.asList(<span class="string">&quot;BestPrice&quot;</span>, <span class="string">&quot;LetsSaveBig&quot;</span>, <span class="string">&quot;MyFavoriteShop&quot;</span>, <span class="string">&quot;BuyItAll&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="type">long</span> <span class="variable">start</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line"></span><br><span class="line">    List&lt;Double&gt; collects = strings.parallelStream()</span><br><span class="line">            .map(s -&gt; &#123;</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    Thread.sleep(<span class="number">1000</span>);</span><br><span class="line">                &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">                    e.printStackTrace();</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">return</span> s.charAt(<span class="number">0</span>) * ThreadLocalRandom.current().nextDouble();</span><br><span class="line">            &#125;)</span><br><span class="line">            .collect(Collectors.toList());</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="type">long</span> <span class="variable">end</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line">    System.out.println(end - start);</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><em><strong>改动之处：stream（）—》parallelStream()</strong></em></p><p>执行时间为：<strong>1067毫秒</strong></p><h5 id="CompletableFuture实现"><a href="#CompletableFuture实现" class="headerlink" title="CompletableFuture实现"></a>CompletableFuture实现</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    List&lt;String&gt; strings = Arrays.asList(<span class="string">&quot;BestPrice&quot;</span>, <span class="string">&quot;LetsSaveBig&quot;</span>, <span class="string">&quot;MyFavoriteShop&quot;</span>, <span class="string">&quot;BuyItAll&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="type">long</span> <span class="variable">start</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line"></span><br><span class="line">    List&lt;CompletableFuture&lt;Double&gt;&gt; collect = strings.stream()</span><br><span class="line">            .map(s -&gt;</span><br><span class="line">                    CompletableFuture.supplyAsync(() -&gt; &#123;</span><br><span class="line">                        <span class="keyword">try</span> &#123;</span><br><span class="line">                            Thread.sleep(<span class="number">1000</span>);</span><br><span class="line">                        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">                            e.printStackTrace();</span><br><span class="line">                        &#125;</span><br><span class="line">                        <span class="keyword">return</span> s.charAt(<span class="number">0</span>) * ThreadLocalRandom.current().nextDouble();</span><br><span class="line">                    &#125;)</span><br><span class="line">            )</span><br><span class="line">            .collect(Collectors.toList());</span><br><span class="line"></span><br><span class="line">    List&lt;Double&gt; collect1 = collect.stream()</span><br><span class="line">            .map(CompletableFuture::join)</span><br><span class="line">            .collect(Collectors.toList());</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="type">long</span> <span class="variable">end</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line">    System.out.println(end - start);</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>执行时间为：<strong>1072毫秒</strong></p><p>可以看到，使用并行流和CompletableFuture实现比原先顺序流要快了很多，而并行流和CompletableFuture好像并没有多大差别。这是因为什么呢？</p><h5 id="并行流和CompletableFuture差别"><a href="#并行流和CompletableFuture差别" class="headerlink" title="并行流和CompletableFuture差别"></a>并行流和CompletableFuture差别</h5><p>并行流和CompletableFuture内部采用的是同样的通用线程池也就是上文说的<strong>ForkJoinPool</strong> ，默认都使用固定数目的线程，具体线程数取决于<code>Runtime.getRuntime().availableProcessors()</code>的返回值。</p><p>而CompletableFuture不止可以使用默认的线程池，我们也可以根据自己的实际需求提供对应的线程池对象，尤其是线程池的大小，这是并行流API无法提供的，而CompletableFuture也并非仅仅为了实现多线程，更重要是是弥补Future接口使用带来的缺陷和不足。</p><h5 id="执行流程图"><a href="#执行流程图" class="headerlink" title="执行流程图"></a>执行流程图</h5><p><img src="https://pic.imgdb.cn/item/64795c9ff024cca173266bdd.png"></p><p>而CompletableFuture实现则是先用顺序流返回了一个CompletableFuture对象，相当于收集了一个多线程集合，然后顺序启动多线程。</p><h4 id="CompletableFuture-常用方法"><a href="#CompletableFuture-常用方法" class="headerlink" title="CompletableFuture 常用方法"></a>CompletableFuture 常用方法</h4><h5 id="获取结果"><a href="#获取结果" class="headerlink" title="获取结果"></a>获取结果</h5><ul><li>public T get( )  实现了Future接口的方法，等待线程执行获取结果，可能会阻塞线程去等待</li><li>public T get(long timeout, TimeUnit unit)     实现了Future接口的方法，等待线程获取结果，超过指定时间抛出异常</li><li>public T getNow(T valuelfAbsent)                   相当于三元运算，需要立即获取结果，如果没有则使用valuelfAbsent</li><li>public T join( ):                                                     join方法和get( ) 方法作用几乎,不同的是,join方法不抛出异常</li></ul><p>简单演示一下getNow()方法</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line"></span><br><span class="line">      CompletableFuture&lt;Integer&gt; future = CompletableFuture.supplyAsync(() -&gt; &#123;</span><br><span class="line">          <span class="keyword">try</span> &#123; TimeUnit.SECONDS.sleep(<span class="number">1</span>);  &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;e.printStackTrace();&#125;</span><br><span class="line">          <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">      &#125;);</span><br><span class="line">      <span class="type">Integer</span> <span class="variable">now1</span> <span class="operator">=</span> future.getNow(<span class="number">3</span>);</span><br><span class="line">      <span class="keyword">try</span> &#123; TimeUnit.SECONDS.sleep(<span class="number">2</span>);  &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;e.printStackTrace();&#125;</span><br><span class="line"></span><br><span class="line">      <span class="type">Integer</span> <span class="variable">now2</span> <span class="operator">=</span> future.getNow(<span class="number">3</span>);</span><br><span class="line">      System.out.println(<span class="string">&quot;now1: &quot;</span>+ now1);</span><br><span class="line">      System.out.println(<span class="string">&quot;now2: &quot;</span>+ now2);</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure><p>结果：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">now1: <span class="number">3</span></span><br><span class="line">now2: <span class="number">1</span></span><br><span class="line"></span><br><span class="line">Process finished with exit code <span class="number">0</span></span><br></pre></td></tr></table></figure><h5 id="对计算结果进行处理"><a href="#对计算结果进行处理" class="headerlink" title="对计算结果进行处理"></a>对计算结果进行处理</h5><h6 id="thenApply、handle"><a href="#thenApply、handle" class="headerlink" title="(thenApply、handle)"></a><strong>(thenApply、handle)</strong></h6><p>**<code>thenApply(Function&lt;? super T,? extends U&gt; fn)</code>**：</p><ul><li>该方法用于在异步任务完成后对结果进行转换，返回一个新的 <code>CompletableFuture</code> 对象。</li><li>它接受一个 <code>Function</code> 参数，该函数将任务的结果类型 <code>T</code> 转换为类型 <code>U</code>。</li><li><code>thenApply()</code> 方法会等待当前任务完成，并将任务的结果作为参数传递给函数，然后返回一个新的 <code>CompletableFuture</code> 对象，该对象表示对结果进行转换后的值。</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line"></span><br><span class="line">    CompletableFuture&lt;String&gt; future = CompletableFuture.supplyAsync(() -&gt; <span class="string">&quot;Hello&quot;</span>);</span><br><span class="line">    CompletableFuture&lt;Integer&gt; future1 = future.thenApply(String::length);</span><br><span class="line">    CompletableFuture&lt;Integer&gt; future2 = future1.thenApply(s -&gt; s * <span class="number">100</span> +<span class="number">88</span>);</span><br><span class="line">    System.out.println(future2.join());</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>结果</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">588</span></span><br><span class="line"></span><br><span class="line">Process finished with exit code <span class="number">0</span></span><br></pre></td></tr></table></figure><p>可以看到thenApply就是把上一个结果传递给下一个<code>CompletableFuture</code> 对象，相当于链式调用。</p><p>上述代码也可以这样实现，也就是链式调用</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">CompletableFuture&lt;Integer&gt; future = CompletableFuture.supplyAsync(() -&gt; <span class="string">&quot;Hello&quot;</span>)</span><br><span class="line">            .thenApply(String::length)</span><br><span class="line">            .thenApply(s -&gt; s * <span class="number">100</span> +<span class="number">88</span>);</span><br><span class="line"></span><br><span class="line">System.out.println(future.join());</span><br></pre></td></tr></table></figure><p>**<code>handle(BiFunction&lt;? super T,Throwable,? extends U&gt; fn)</code>**：</p><ul><li>该方法用于在异步任务完成后对结果进行处理，无论任务是否发生异常，都会执行处理逻辑，并返回一个新的 <code>CompletableFuture</code> 对象。</li><li>它接受一个 <code>BiFunction</code> 参数，该函数接受任务的结果类型 <code>T</code> 和可能发生的异常类型 <code>Throwable</code>，并返回类型 <code>U</code>。</li><li><code>handle()</code> 方法会等待当前任务完成，并将任务的结果或异常作为参数传递给函数，然后返回一个新的 <code>CompletableFuture</code> 对象，该对象表示处理后的结果。</li></ul><p>handle方法和thenApply方法类似，不同的是handle在中间操作时出现异常不会停止任务，而是把异常传递给下一个Completable对象</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line"></span><br><span class="line">    CompletableFuture&lt;Integer&gt; future = CompletableFuture.supplyAsync(() -&gt; <span class="number">10</span>)</span><br><span class="line">                    .handle((result, ex) -&gt; &#123;</span><br><span class="line">                            <span class="keyword">if</span> (ex != <span class="literal">null</span>) &#123;</span><br><span class="line">                                System.out.println(<span class="string">&quot;Exception occurred: &quot;</span> + ex);</span><br><span class="line">                                <span class="keyword">return</span> -<span class="number">1</span>;  <span class="comment">// 处理异常情况，返回默认值</span></span><br><span class="line">                            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                                <span class="keyword">return</span> result * <span class="number">2</span>;  <span class="comment">// 处理正常情况，返回结果的两倍</span></span><br><span class="line">                            &#125;</span><br><span class="line">                     &#125;);</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    System.out.println(future.join());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>结果</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">20</span></span><br><span class="line"></span><br><span class="line">Process finished with exit code <span class="number">0</span></span><br></pre></td></tr></table></figure><p>稍微改动一点</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">CompletableFuture&lt;Integer&gt; future = CompletableFuture.supplyAsync(() -&gt; <span class="number">1</span>/<span class="number">0</span>)</span><br><span class="line">                .handle((result, ex) -&gt; &#123;</span><br><span class="line">                        <span class="keyword">if</span> (ex != <span class="literal">null</span>) &#123;</span><br><span class="line">                            System.out.println(<span class="string">&quot;Exception occurred: &quot;</span> + ex);</span><br><span class="line">                            <span class="keyword">return</span> -<span class="number">1</span>;  <span class="comment">// 处理异常情况，返回默认值</span></span><br><span class="line">                        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                            <span class="keyword">return</span> result * <span class="number">2</span>;  <span class="comment">// 处理正常情况，返回结果的两倍</span></span><br><span class="line">                        &#125;</span><br><span class="line">                 &#125;);</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">System.out.println(future.join());</span><br></pre></td></tr></table></figure><p>结果</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Exception occurred: java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero</span><br><span class="line">-<span class="number">1</span></span><br><span class="line"></span><br><span class="line">Process finished with exit code <span class="number">0</span></span><br></pre></td></tr></table></figure><p>也就是出现了异常并捕获，但是并没有影响结果执行。</p><h5 id="对计算结果进行消费"><a href="#对计算结果进行消费" class="headerlink" title="对计算结果进行消费"></a>对计算结果进行消费</h5><h6 id="thenRun、thenAccept、thenApply"><a href="#thenRun、thenAccept、thenApply" class="headerlink" title="(thenRun、thenAccept、thenApply)"></a><strong>(thenRun、thenAccept、thenApply)</strong></h6><p><strong><code>thenRun(Runnable action)</code></strong>:</p><ul><li>该方法在异步任务完成后执行指定的操作，不接收任务的结果。</li><li>它接受一个 <code>Runnable</code> 参数，表示要执行的操作。</li><li><code>thenRun()</code> 方法会等待当前任务完成后，执行指定的操作，无论前一个任务的结果是什么。</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line"></span><br><span class="line">    CompletableFuture&lt;Void&gt; future = CompletableFuture.supplyAsync(() -&gt; <span class="string">&quot;Hello&quot;</span>)</span><br><span class="line">            .thenRun(() -&gt; System.out.println(<span class="string">&quot;Task completed&quot;</span>));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>结果</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Task completed</span><br><span class="line"></span><br><span class="line">Process finished with exit code <span class="number">0</span></span><br></pre></td></tr></table></figure><p><strong><code>thenAccept(Consumer&lt;? super T&gt; action)</code></strong>:</p><ul><li>该方法在异步任务完成后对结果进行消费，不返回任何结果。</li><li>它接受一个 <code>Consumer</code> 参数，表示对结果进行消费的操作。</li><li><code>thenAccept()</code> 方法会等待当前任务完成后，将任务的结果作为参数传递给操作进行消费。</li></ul><p>通过传参可以看到，该方法是一个消费型接口，对传入数据进行消费，不返回结果。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line"></span><br><span class="line">    CompletableFuture.supplyAsync(() -&gt; <span class="string">&quot;Hello&quot;</span>)</span><br><span class="line">            .thenApply(s -&gt; s+ <span class="string">&quot; world&quot;</span>)</span><br><span class="line">            .thenAccept(s -&gt; System.out.println(<span class="string">&quot;Result: &quot;</span> + s));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>结果：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Result: Hello world</span><br><span class="line"></span><br><span class="line">Process finished with exit code <span class="number">0</span></span><br></pre></td></tr></table></figure><p><code>thenApply方法同上</code></p><h5 id="对计算结果进行合并"><a href="#对计算结果进行合并" class="headerlink" title="对计算结果进行合并"></a>对计算结果进行合并</h5><h6 id="thenCombine、thenAcceptBoth、runAfterBoth"><a href="#thenCombine、thenAcceptBoth、runAfterBoth" class="headerlink" title="(thenCombine、thenAcceptBoth、runAfterBoth)"></a><strong>(thenCombine、thenAcceptBoth、runAfterBoth)</strong></h6><p><strong><code>thenCombine(CompletableFuture&lt;? extends U&gt; other, BiFunction&lt;? super T, ? super U, ? extends V&gt; fn)</code></strong>:</p><ul><li>该方法用于组合两个异步任务的结果，并在两个任务都完成后执行指定的操作，并返回一个新的 <code>CompletableFuture</code> 对象。</li><li>它接受另一个 <code>CompletableFuture</code> 对象 <code>other</code> 和一个 <code>BiFunction</code> 参数，该函数接受两个任务的结果类型 <code>T</code> 和 <code>U</code>，并返回类型 <code>V</code>。</li><li><code>thenCombine()</code> 方法会等待当前任务和 <code>other</code> 任务都完成后，将两个任务的结果作为参数传递给函数，并返回一个新的 <code>CompletableFuture</code> 对象，表示对结果进行组合后的值。</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line"></span><br><span class="line">    CompletableFuture&lt;Integer&gt; future1 = CompletableFuture.supplyAsync(() -&gt; <span class="number">10</span>);</span><br><span class="line">    CompletableFuture&lt;Integer&gt; future2 = CompletableFuture.supplyAsync(() -&gt; <span class="number">20</span>)</span><br><span class="line">                            .thenCombine(future1, (s1,s2) -&gt; s1*s2);</span><br><span class="line">    System.out.println(future2.join());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>结果</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">200</span></span><br><span class="line"></span><br><span class="line">Process finished with exit code <span class="number">0</span></span><br></pre></td></tr></table></figure><p><strong><code>thenAcceptBoth(CompletableFuture&lt;? extends U&gt; other, BiConsumer&lt;? super T, ? super U&gt; action)</code></strong>:</p><ul><li>该方法用于在两个异步任务都完成后执行指定的操作，不返回任何结果。</li><li>它接受另一个 <code>CompletableFuture</code> 对象 <code>other</code> 和一个 <code>BiConsumer</code> 参数，该函数接受两个任务的结果类型 <code>T</code> 和 <code>U</code>，并执行指定的操作。</li><li><code>thenAcceptBoth()</code> 方法会等待当前任务和 <code>other</code> 任务都完成后，将两个任务的结果作为参数传递给操作进行消费。</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line"></span><br><span class="line">    CompletableFuture&lt;Integer&gt; future1 = CompletableFuture.supplyAsync(() -&gt; <span class="number">10</span>);</span><br><span class="line">    CompletableFuture&lt;Integer&gt; future2 = CompletableFuture.supplyAsync(() -&gt; <span class="number">20</span>);</span><br><span class="line">    CompletableFuture&lt;Void&gt; combinedFuture = future1.</span><br><span class="line">            thenAcceptBoth(future2, (result1, result2) -&gt;</span><br><span class="line">                    System.out.println(<span class="string">&quot;Combined result: &quot;</span> + (result1 + result2)));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用方法与thenCombine类似，区别在于thenCombine有返回，而thenAcceptBoth无返回值</p><p>结果不言而喻</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Combined result: <span class="number">30</span></span><br><span class="line"></span><br><span class="line">Process finished with exit code <span class="number">0</span></span><br></pre></td></tr></table></figure><p><strong><code>runAfterBoth(CompletableFuture&lt;?&gt; other, Runnable action)</code></strong>:</p><ul><li><p>该方法用于在两个异步任务都完成后执行指定的操作，不接收任务的结果。</p></li><li><p>它接受另一个 <code>CompletableFuture</code> 对象 <code>other</code> 和一个 <code>Runnable</code> 参数，表示要执行的操作。</p></li><li><p><code>runAfterBoth()</code> 方法会等待当前任务和 <code>other</code> 任务都完成后，执行指定的操作。</p></li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line"></span><br><span class="line">    CompletableFuture&lt;Integer&gt; future1 = CompletableFuture.supplyAsync(() -&gt; <span class="number">10</span>);</span><br><span class="line">    CompletableFuture&lt;Integer&gt; future2 = CompletableFuture.supplyAsync(() -&gt; <span class="number">20</span>);</span><br><span class="line">    CompletableFuture&lt;Void&gt; combinedFuture = future1.</span><br><span class="line">            runAfterBoth(future2, () -&gt; System.out.println(<span class="string">&quot;Both tasks completed&quot;</span>));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>相较于上面两个方法，runAfterBoth 又有些不同，他既不接收返回值，也不返回，而是等待两个future执行好了之后在去执行，所以一般用于在程序结束</p><p>结果</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Both tasks completed</span><br><span class="line"></span><br><span class="line">Process finished with exit code <span class="number">0</span></span><br></pre></td></tr></table></figure><h5 id="对计算速度进行选用"><a href="#对计算速度进行选用" class="headerlink" title="对计算速度进行选用"></a>对计算速度进行选用</h5><h6 id="applyToEither、acceptEither、runAfterEither"><a href="#applyToEither、acceptEither、runAfterEither" class="headerlink" title="(applyToEither、acceptEither、runAfterEither)"></a><strong>(applyToEither、acceptEither、runAfterEither)</strong></h6><p><strong><code>applyToEither(CompletableFuture&lt;? extends T&gt; other, Function&lt;? super T, U&gt; fn)</code></strong>:</p><ul><li>该方法接受另一个 <code>CompletableFuture</code> 对象 <code>other</code> 和一个 <code>Function</code> 参数，并返回一个新的 <code>CompletableFuture</code> 对象。</li><li><code>applyToEither()</code> 方法会等待当前任务和 <code>other</code> 任务中的任意一个完成，并将首先完成的任务的结果作为参数传递给函数进行转换。</li><li>返回的 <code>CompletableFuture</code> 对象的结果类型为 <code>U</code>，表示对首先完成任务的结果进行转换后的值。</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line"></span><br><span class="line">    CompletableFuture&lt;Integer&gt; future1 = CompletableFuture.supplyAsync(() -&gt; &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            Thread.sleep(<span class="number">2000</span>);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="number">10</span>;</span><br><span class="line">    &#125;);</span><br><span class="line">    CompletableFuture&lt;Integer&gt; future2 = CompletableFuture.supplyAsync(() -&gt; &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            Thread.sleep(<span class="number">1000</span>);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="number">20</span>;</span><br><span class="line">    &#125;);</span><br><span class="line"></span><br><span class="line">    CompletableFuture&lt;Void&gt; voidCompletableFuture = future1.applyToEither(future2, s -&gt; s * <span class="number">123</span>)</span><br><span class="line">            .thenApply(s -&gt; <span class="string">&quot;Result: &quot;</span> + s)</span><br><span class="line">            .thenAccept(res -&gt; &#123;</span><br><span class="line">                System.out.println(<span class="string">&quot;--------------------------&quot;</span>);</span><br><span class="line">                System.out.println(res);</span><br><span class="line">            &#125;);</span><br><span class="line">    voidCompletableFuture.join();</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>结果</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">--------------------------</span><br><span class="line">Result: <span class="number">2460</span></span><br><span class="line"></span><br><span class="line">Process finished with exit code <span class="number">0</span></span><br></pre></td></tr></table></figure><p>很明显返回的是 <strong>future2 的20</strong></p><p><strong><code> acceptEither(CompletionStage&lt;? extends T&gt; other, Consumer&lt;? super T&gt; action)</code></strong></p><p><code>acceptEither</code> 是 <code>CompletableFuture</code> 类中的一个方法，用于处理两个独立的异步任务，并且只接受其中一个操作的结果。可以这么理解：如果第一个异步任务完成了，则使用它的结果进行处理，否则使用第二个异步任务的结果</p><p>示例 </p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line"></span><br><span class="line">    CompletableFuture&lt;Integer&gt; future1 = CompletableFuture.supplyAsync(() -&gt; &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            Thread.sleep(<span class="number">500</span>);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="number">10</span>;</span><br><span class="line">    &#125;);</span><br><span class="line"></span><br><span class="line">    CompletableFuture&lt;Integer&gt; future2 = CompletableFuture.supplyAsync(() -&gt; &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            Thread.sleep(<span class="number">1000</span>);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="number">20</span>;</span><br><span class="line">    &#125;);</span><br><span class="line"></span><br><span class="line">    future1.acceptEither(future2, System.out::println).join();</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>结果</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">10</span></span><br><span class="line"></span><br><span class="line">Process finished with exit code <span class="number">0</span></span><br></pre></td></tr></table></figure><p><strong>runAfterEither方法签名</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> CompletionStage&lt;Void&gt; <span class="title function_">runAfterEither</span><span class="params">(CompletionStage&lt;?&gt; other, Runnable action)</span>;</span><br></pre></td></tr></table></figure><p><code>runAfterEither</code> 方法接收两个参数：一个是另一个 <code>CompletionStage</code> 对象，另一个是一个 <code>Runnable</code> 或者 <code>Consumer</code> 对象。当其中任意一个 <code>CompletionStage</code> 完成（无论是正常完成还是异常完成），就会执行指定的操作。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">CompletableFuture&lt;Integer&gt; future1 = CompletableFuture.supplyAsync(() -&gt; &#123;</span><br><span class="line">    <span class="comment">// 执行某个任务</span></span><br><span class="line">    <span class="keyword">return</span> <span class="number">42</span>;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">CompletableFuture&lt;String&gt; future2 = CompletableFuture.supplyAsync(() -&gt; &#123;</span><br><span class="line">    <span class="comment">// 执行另一个任务</span></span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;Hello&quot;</span>;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">future1.runAfterEither(future2, () -&gt; &#123;</span><br><span class="line">    System.out.println(<span class="string">&quot;执行了操作&quot;</span>);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>我们创建了两个 <code>CompletableFuture</code> 对象 <code>future1</code> 和 <code>future2</code>，分别代表两个异步任务。然后，我们调用 <code>runAfterEither</code> 方法，传递了这两个 <code>CompletableFuture</code> 对象和一个 <code>Runnable</code> 对象作为参数。当 <code>future1</code> 或者 <code>future2</code> 中的任意一个完成时，即可执行传入的操作，这里是简单地打印一条消息。</p><h5 id="多任务组合"><a href="#多任务组合" class="headerlink" title="多任务组合"></a>多任务组合</h5><p><strong><code>allOf(CompletableFuture&lt;?&gt;... futures)</code></strong>:</p><ul><li>该方法接受一个 <code>CompletableFuture</code> 数组作为参数，并返回一个新的 <code>CompletableFuture</code> 对象。</li><li><code>allOf()</code> 方法等待所有给定的任务都完成后，返回一个新的 <code>CompletableFuture</code> 对象。</li><li>返回的 <code>CompletableFuture</code> 对象的结果为 <code>Void</code>，表示所有任务都已完成。</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line"></span><br><span class="line">    CompletableFuture&lt;String&gt; future1 = CompletableFuture.supplyAsync(() -&gt; <span class="string">&quot;Result 1&quot;</span>);</span><br><span class="line">    CompletableFuture&lt;Integer&gt; future2 = CompletableFuture.supplyAsync(() -&gt; <span class="number">42</span>);</span><br><span class="line">    CompletableFuture&lt;Boolean&gt; future3 = CompletableFuture.supplyAsync(() -&gt; <span class="literal">true</span>);</span><br><span class="line"></span><br><span class="line">    CompletableFuture&lt;Void&gt; allFutures = CompletableFuture.allOf(future1, future2, future3);</span><br><span class="line">    allFutures.thenRun(() -&gt; System.out.println(<span class="string">&quot;All tasks completed&quot;</span>));</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>allof方法使用类似于遍历一个CompletableFuture数组并执行，不同是的，他会等待所有结果出来后才会打印 thenRun()后得内容。而不返回任何内容。</p><p>结果不言而喻</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">All tasks completed</span><br><span class="line"></span><br><span class="line">Process finished with exit code <span class="number">0</span></span><br></pre></td></tr></table></figure><p><strong><code>anyOf(CompletableFuture&lt;?&gt;... futures)</code></strong>:</p><ul><li>该方法接受一个 <code>CompletableFuture</code> 数组作为参数，并返回一个新的 <code>CompletableFuture</code> 对象。</li><li><code>anyOf()</code> 方法等待任何一个给定的任务完成后，返回一个新的 <code>CompletableFuture</code> 对象。</li><li>返回的 <code>CompletableFuture</code> 对象的结果为第一个完成的任务的结果。</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line"></span><br><span class="line">    CompletableFuture&lt;String&gt; future1 = CompletableFuture.supplyAsync(() -&gt; &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            Thread.sleep(<span class="number">2000</span>);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">&quot;Result 1&quot;</span>;</span><br><span class="line">    &#125;);</span><br><span class="line">    CompletableFuture&lt;Integer&gt; future2 = CompletableFuture.supplyAsync(() -&gt; &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            Thread.sleep(<span class="number">1000</span>);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="number">42</span>;</span><br><span class="line">    &#125;);</span><br><span class="line">    CompletableFuture&lt;Boolean&gt; future3 = CompletableFuture.supplyAsync(() -&gt; &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            Thread.sleep(<span class="number">3000</span>);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;);</span><br><span class="line"></span><br><span class="line">    CompletableFuture&lt;Object&gt; anyFuture = CompletableFuture.anyOf(future1, future2, future3);</span><br><span class="line">    System.out.println(anyFuture.join());</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>我们创建了三个Completable对象，并一次加入一个新的CompletableFuture.anyof方法中，三个线程分别等待不同的时间，我们预计anyFuture接收第一个运行结束并返回得CompletableFuture对象</p><p>结果</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">42</span></span><br><span class="line"></span><br><span class="line">Process finished with exit code <span class="number">0</span></span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;\assets\css\APlayer.min.css&quot;&gt;&lt;script src=&quot;\assets\js\APlayer.min.js&quot; cla</summary>
      
    
    
    
    <category term="Java" scheme="https://nerkeler.github.io/categories/Java/"/>
    
    
    <category term="JUC" scheme="https://nerkeler.github.io/tags/JUC/"/>
    
    <category term="多线程" scheme="https://nerkeler.github.io/tags/%E5%A4%9A%E7%BA%BF%E7%A8%8B/"/>
    
  </entry>
  
  <entry>
    <title>synchronized和lock</title>
    <link href="https://nerkeler.github.io/2023/05/29/synchronized%E5%92%8Clock/"/>
    <id>https://nerkeler.github.io/2023/05/29/synchronized%E5%92%8Clock/</id>
    <published>2023-05-29T08:31:00.000Z</published>
    <updated>2025-05-25T07:23:13.793Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h1 id="synchronized和lock"><a href="#synchronized和lock" class="headerlink" title="synchronized和lock"></a>synchronized和lock</h1><h2 id="synchronized"><a href="#synchronized" class="headerlink" title="synchronized"></a>synchronized</h2><p>synchronized关键字用于Java中同步方法或同步代码块中，防止资源冲突。当任务要执行被synchronized关键字保护的代码片段的时候，它将检查锁是否可用，然后获取锁，执行代码，释放锁。</p><h3 id="基本使用"><a href="#基本使用" class="headerlink" title="基本使用"></a>基本使用</h3><h4 id="同步方法"><a href="#同步方法" class="headerlink" title="同步方法"></a>同步方法</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title function_">synchronizedMethod</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">// 该方法的内容在同一时间只能被一个线程执行</span></span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="同步代码块"><a href="#同步代码块" class="headerlink" title="同步代码块"></a>同步代码块</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">someMethod</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">// 非同步的代码</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">synchronized</span> (<span class="built_in">this</span>) &#123;</span><br><span class="line">        <span class="comment">// 被同步的代码块</span></span><br><span class="line">        <span class="comment">// ...</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 非同步的代码</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>其余不在赘述，相关使用应该很常见</p><h2 id="lock"><a href="#lock" class="headerlink" title="lock"></a>lock</h2><h3 id="ReentrantLock"><a href="#ReentrantLock" class="headerlink" title="ReentrantLock"></a>ReentrantLock</h3><p>除了使用synchronized外，我们可以使用Lock接口写的<code>ReentrantLock</code> 实现独占锁的功能。</p><p>查看类图可知，<code>ReentrantLock</code> 实现了Lock接口</p><p><img src="https://pic1.imgdb.cn/item/64741b67f024cca1738eebeb.png"></p><p>Lock接口定义了几个方法用来实现锁的基本功能</p><ul><li><code>lock() : void</code>           &#x2F;&#x2F;获得锁。</li><li><code>lockInterruptibly() : void</code>    &#x2F;&#x2F; 获取锁定，除非当前线程是 <a href="../../../../java/lang/Thread.html#interrupt--">interrupted</a> 。 </li><li><code>tryLock() : boolean</code>                   &#x2F;&#x2F;   只有在调用时才可以获得锁</li><li><code>tryLock(time : long,unit : TimeUnit ) : boolean</code>   &#x2F;&#x2F; 如果在给定的等待时间内是空闲的，并且当前的线程尚未得到 <a href="../../../../java/lang/Thread.html#interrupt--">interrupted，</a>则获取该锁。 </li><li><code>unlock() : void</code>                           &#x2F;&#x2F;  释放锁。</li><li><code>newCondition() : Condition</code>    &#x2F;&#x2F; 返回一个新<a href="../../../../java/util/concurrent/locks/Condition.html"><code>Condition</code></a>绑定到该实例<code>Lock</code>实例。</li></ul><p>常用方法应该是lock()&#x2F;unlock()和newCondition(),简要阐述一下，由ReentrantLock实现</p><h4 id="lock和unlock"><a href="#lock和unlock" class="headerlink" title="lock和unlock"></a>lock和unlock</h4><p>lock和unlock方法是Lock接口提供的重要的加锁解锁方法，他们包围的代码片段就是相应的加锁处理片段，下面是一个简单的示例</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">LockExample</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="type">Lock</span> <span class="variable">lock</span>   <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ReentrantLock</span>();</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="type">Object</span> <span class="variable">object</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Object</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span>  <span class="keyword">void</span> <span class="title function_">test</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        lock.lock();</span><br><span class="line">        System.out.println(<span class="string">&quot;lock 测试&quot;</span>+ object.toString());</span><br><span class="line">        lock.unlock();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>只需要新建Lock对象后在需要锁的开始和结束处使用lock方法和unlock方法进行加锁和解锁就可以完成相应的功能。</p><p>需要注意，如果锁内部代码出现异常可能会造成lock对象的unlock方法没有执行，从而未释放锁。所以相应的功能应该卸载try catch代码块中</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">LockExample</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="type">Lock</span> <span class="variable">lock</span>   <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ReentrantLock</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="type">Object</span> <span class="variable">object</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Object</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span>  <span class="keyword">void</span> <span class="title function_">test</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        </span><br><span class="line">        lock.lock();</span><br><span class="line">        <span class="keyword">try</span>&#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;lock 测试&quot;</span>+ object.toString());</span><br><span class="line"></span><br><span class="line">        &#125;<span class="keyword">catch</span> (Exception e)&#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">finally</span> &#123;</span><br><span class="line">            lock.unlock();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="newCondition"><a href="#newCondition" class="headerlink" title="newCondition"></a>newCondition</h4><p><code>newCondition</code>()方法是Lock接口中线程通讯的重要实现，他返回一个<code>Condition</code>对象，<code>Condition</code>类似于<code>Object</code>监视器方法（ <a href="../../../../java/lang/Object.html#wait--"><code>wait</code></a> ， <a href="../../../../java/lang/Object.html#notify--"><code>notify</code></a>和<a href="../../../../java/lang/Object.html#notifyAll--"><code>notifyAll</code></a>  ），将其中的重要方法封装成了不同的对象，以得到具有多个等待集的每个对象，通过将它们与使用任意的组合的效果<a href="../../../../java/util/concurrent/locks/Lock.html"><code>Lock</code>个</a>实现。  <code>Lock</code>替换<code>synchronized</code>方法和语句的使用，  <code>Condition</code>取代了对象监视器方法的使用。 下面是一个简单的示例</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.juc;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.util.concurrent.locks.Condition;</span><br><span class="line"><span class="keyword">import</span> java.util.concurrent.locks.Lock;</span><br><span class="line"><span class="keyword">import</span> java.util.concurrent.locks.ReentrantLock;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="comment">// 定义一个共享的计数器</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="variable">count</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 定义一个锁和条件变量</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">Lock</span> <span class="variable">lock</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ReentrantLock</span>();</span><br><span class="line">    <span class="keyword">private</span> <span class="type">Condition</span> <span class="variable">condition</span> <span class="operator">=</span> lock.newCondition();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 定义一个增加计数器的方法</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">increment</span><span class="params">()</span> &#123;</span><br><span class="line">        lock.lock();</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            count++;</span><br><span class="line">            System.out.println(<span class="string">&quot;Counter value: &quot;</span> + count);</span><br><span class="line">            <span class="comment">// 通知其他线程，计数器已经增加</span></span><br><span class="line">            condition.signal();</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            lock.unlock();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 定义一个减少计数器的方法</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">decrement</span><span class="params">()</span> &#123;</span><br><span class="line">        lock.lock();</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="keyword">while</span> (count == <span class="number">0</span>) &#123;</span><br><span class="line">                <span class="comment">// 等待其他线程通知，计数器已经增加</span></span><br><span class="line">                condition.await();</span><br><span class="line">            &#125;</span><br><span class="line">            count--;</span><br><span class="line">            System.out.println(<span class="string">&quot;Counter value: &quot;</span> + count);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            lock.unlock();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">Counter</span> <span class="variable">counter</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Counter</span>();</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 创建两个线程，一个增加计数器，一个减少计数器</span></span><br><span class="line">        <span class="type">Thread</span> <span class="variable">t1</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(() -&gt; &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">5</span>; i++) &#123;</span><br><span class="line">                counter.increment();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line"></span><br><span class="line">        <span class="type">Thread</span> <span class="variable">t2</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(() -&gt; &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">5</span>; i++) &#123;</span><br><span class="line">                counter.decrement();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 启动线程</span></span><br><span class="line">        t1.start();</span><br><span class="line">        t2.start();</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 等待线程结束</span></span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            t1.join();</span><br><span class="line">            t2.join();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>该类定义了一个资源类和两个方法，一个增加计数，一个减少计数，循环5次，后通知另一线程执行，结果如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">Counter value: <span class="number">1</span></span><br><span class="line">Counter value: <span class="number">2</span></span><br><span class="line">Counter value: <span class="number">3</span></span><br><span class="line">Counter value: <span class="number">4</span></span><br><span class="line">Counter value: <span class="number">5</span></span><br><span class="line">Counter value: <span class="number">4</span></span><br><span class="line">Counter value: <span class="number">3</span></span><br><span class="line">Counter value: <span class="number">2</span></span><br><span class="line">Counter value: <span class="number">1</span></span><br><span class="line">Counter value: <span class="number">0</span></span><br></pre></td></tr></table></figure><p><strong>condition的使用类似于Object方法中的wait和notify方法，不同的是Condition类可以提供多个条件，实现更细粒度的线程等待和通知</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.juc;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.util.concurrent.locks.Condition;</span><br><span class="line"><span class="keyword">import</span> java.util.concurrent.locks.Lock;</span><br><span class="line"><span class="keyword">import</span> java.util.concurrent.locks.ReentrantLock;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MultiConditionExample</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">Lock</span> <span class="variable">lock</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ReentrantLock</span>();</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">Condition</span> <span class="variable">condition1</span> <span class="operator">=</span> lock.newCondition();</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">Condition</span> <span class="variable">condition2</span> <span class="operator">=</span> lock.newCondition();</span><br><span class="line">    <span class="keyword">private</span> <span class="type">boolean</span> <span class="variable">condition1Met</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">boolean</span> <span class="variable">condition2Met</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">awaitCondition1</span><span class="params">()</span> <span class="keyword">throws</span> InterruptedException &#123;</span><br><span class="line">        lock.lock();</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="keyword">while</span> (!condition1Met) &#123;</span><br><span class="line">                condition1.await();</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">// condition1Met 为 true，执行相关操作</span></span><br><span class="line">            System.out.println(<span class="string">&quot;Condition 1 开始运行&quot;</span>);</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            lock.unlock();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">awaitCondition2</span><span class="params">()</span> <span class="keyword">throws</span> InterruptedException &#123;</span><br><span class="line">        lock.lock();</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="keyword">while</span> (!condition2Met) &#123;</span><br><span class="line">                condition2.await();</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">// condition2Met 为 true，执行相关操作</span></span><br><span class="line">            System.out.println(<span class="string">&quot;Condition 2 met&quot;</span>);</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            lock.unlock();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">signalCondition1</span><span class="params">()</span> &#123;</span><br><span class="line">        lock.lock();</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            condition1Met = <span class="literal">true</span>;</span><br><span class="line">            condition1.signal();</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            lock.unlock();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">signalCondition2</span><span class="params">()</span> &#123;</span><br><span class="line">        lock.lock();</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            condition2Met = <span class="literal">true</span>;</span><br><span class="line">            condition2.signal();</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            lock.unlock();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">MultiConditionExample</span> <span class="variable">example</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MultiConditionExample</span>();</span><br><span class="line"></span><br><span class="line">        <span class="type">Thread</span> <span class="variable">thread1</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(() -&gt; &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                Thread.sleep(<span class="number">2000</span>);</span><br><span class="line">                example.signalCondition1();</span><br><span class="line">            &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">                e.printStackTrace();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line"></span><br><span class="line">        <span class="type">Thread</span> <span class="variable">thread2</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(() -&gt; &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                Thread.sleep(<span class="number">3000</span>);</span><br><span class="line">                example.signalCondition2();</span><br><span class="line">            &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">                e.printStackTrace();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line"></span><br><span class="line">        <span class="type">Thread</span> <span class="variable">thread3</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(() -&gt; &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                example.awaitCondition1();</span><br><span class="line">            &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">                e.printStackTrace();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line"></span><br><span class="line">        <span class="type">Thread</span> <span class="variable">thread4</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(() -&gt; &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                example.awaitCondition2();</span><br><span class="line">            &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">                e.printStackTrace();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line"></span><br><span class="line">        thread1.start();</span><br><span class="line">        thread2.start();</span><br><span class="line">        thread3.start();</span><br><span class="line">        thread4.start();</span><br><span class="line"></span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            thread1.join();</span><br><span class="line">            thread2.join();</span><br><span class="line">            thread3.join();</span><br><span class="line">            thread4.join();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>结果</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Condition <span class="number">1</span> met</span><br><span class="line">Condition <span class="number">2</span> met</span><br></pre></td></tr></table></figure><p>这个示例只是将一对Condition信号复制了一份放在一个类中执行，不同于wait&#x2F;notify，Condition在这个可以根据实例信号（condition1&#x2F;condition2)来指定需要通讯的线程。</p><h2 id="Synchronized和Lock对比"><a href="#Synchronized和Lock对比" class="headerlink" title="Synchronized和Lock对比"></a>Synchronized和Lock对比</h2><ol><li><strong>获取方式</strong>：<ul><li><code>Synchronized</code>：通过 <code>synchronized</code> 关键字来实现，可以修饰方法或代码块。</li><li><code>Lock</code>：通过 <code>Lock</code> 接口的实现类（如 <code>ReentrantLock</code>）来创建实例，并调用其方法进行获取锁。</li></ul></li><li><strong>使用方式</strong>：<ul><li><code>Synchronized</code>：在方法上使用 <code>synchronized</code> 关键字或使用 <code>synchronized</code> 块来指定需要同步的代码块。</li><li><code>Lock</code>：使用 <code>Lock</code> 对象的 <code>lock()</code> 方法获取锁，然后使用 <code>unlock()</code> 方法释放锁。</li></ul></li><li><strong>灵活性</strong>：<ul><li><code>Synchronized</code>：是内置的、<strong>隐式</strong>的锁机制，具有<strong>自动释放锁</strong>的特性，可以很方便地使用，但相对较为简单，提供的功能相对有限。</li><li><code>Lock</code>：是<strong>显式</strong>的锁机制，提供了更多的灵活性和功能，例如可重入性、公平性、条件变量等，但需要手动管理锁的获取和释放。</li></ul></li><li><strong>性能</strong>：<ul><li><code>Synchronized</code>：在 Java 6 之后进行了很多优化，性能已经大幅提升，尤其在无竞争的情况下，性能表现良好。</li><li><code>Lock</code>：性能相对较好，并且在高度竞争的情况下能够提供更好的性能表现。</li></ul></li><li><strong>可中断性</strong>：<ul><li><code>Synchronized</code>：一旦获取到锁，无法被其他线程中断，只能等待锁的释放。</li><li><code>Lock</code>：提供了可中断的获取锁的方法 <code>lockInterruptibly()</code>，可以在等待锁的过程中响应中断。</li></ul></li><li><strong>条件变量</strong>：<ul><li><code>Synchronized</code>：不直接支持条件变量，但可以通过 <code>Object</code> 的 <code>wait()</code>、<code>notify()</code> 和 <code>notifyAll()</code> 方法结合 <code>synchronized</code> 关键字来实现类似的功能。</li><li><code>Lock</code>：提供了 <code>Condition</code> 接口来支持条件变量，可以使用 <code>newCondition()</code> 方法创建条件对象，并使用其 <code>await()</code>、<code>signal()</code> 和 <code>signalAll()</code> 方法进行线程等待和通知。</li></ul></li></ol><h4 id="参考："><a href="#参考：" class="headerlink" title="参考："></a>参考：</h4><p>JDK官方文档：<a href="https://docs.oracle.com/javase/tutorial/essential/concurrency/syncmeth.html">Synchronized Methods (The Java™ Tutorials &gt; Essential Java Classes &gt; Concurrency) (oracle.com)</a></p><p><a href="https://www.cnblogs.com/cz123/p/7435113.html">synchronized VS Lock, wait-notify VS Condition</a></p><p>chatGPT3.5</p>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;\assets\css\APlayer.min.css&quot;&gt;&lt;script src=&quot;\assets\js\APlayer.min.js&quot; cla</summary>
      
    
    
    
    <category term="Java" scheme="https://nerkeler.github.io/categories/Java/"/>
    
    
    <category term="JUC" scheme="https://nerkeler.github.io/tags/JUC/"/>
    
    <category term="多线程" scheme="https://nerkeler.github.io/tags/%E5%A4%9A%E7%BA%BF%E7%A8%8B/"/>
    
  </entry>
  
  <entry>
    <title>如何超过大多数人</title>
    <link href="https://nerkeler.github.io/2023/05/19/%E5%A6%82%E4%BD%95%E8%B6%85%E8%BF%87%E5%A4%A7%E5%A4%9A%E6%95%B0%E4%BA%BA/"/>
    <id>https://nerkeler.github.io/2023/05/19/%E5%A6%82%E4%BD%95%E8%B6%85%E8%BF%87%E5%A4%A7%E5%A4%9A%E6%95%B0%E4%BA%BA/</id>
    <published>2023-05-19T09:22:00.000Z</published>
    <updated>2025-05-25T07:23:34.022Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h1 id="如何超过大多数人"><a href="#如何超过大多数人" class="headerlink" title="如何超过大多数人"></a>如何超过大多数人</h1><h5 id="转载自-https-coolshell-cn-articles-19464-html-原作者：陈皓（左耳朵耗子）酷-壳-–-CoolShell"><a href="#转载自-https-coolshell-cn-articles-19464-html-原作者：陈皓（左耳朵耗子）酷-壳-–-CoolShell" class="headerlink" title="转载自 https://coolshell.cn/articles/19464.html  原作者：陈皓（左耳朵耗子）酷 壳 – CoolShell****"></a><em><strong>转载自 <a href="https://coolshell.cn/articles/19464.html">https://coolshell.cn/articles/19464.html</a>  原作者：陈皓（左耳朵耗子）</strong><a href="https://link.juejin.cn/?target=https://coolshell.cn/">酷 壳 – CoolShell</a></em>****</h5><figure class="highlight ini"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">ps:读这篇文章前先看看下面这段话，避免误导大家。</span><br><span class="line"></span><br><span class="line">切记，这篇文章不要过度深思（任何东西都无法经得起审视，因为这世上没有同样的成长环境，也没有同样的认知水平同时也**没有适用于所有人的解决方案**；也不要去急着评判里面列出的观点，只需代入到其中适度审视一番自己即可，能**跳脱出来从外人的角度看看现在的自己处在什么样的阶段**就行。具体怎么想怎么做全在你自己去**不断实践中寻找那个适合自己的方案**）</span><br><span class="line"></span><br><span class="line">引用自：https://juejin.cn/post/7207648496978870333</span><br></pre></td></tr></table></figure><p>当你看到这篇文章的标题，你一定对这篇文章产生了巨大的兴趣，因为你的潜意识在告诉你，这是一本人生的“武林秘籍”，而且还是左耳朵写的，一定有干货满满，只要读完，一定可以练就神功并找到超过大多数人的快车道和捷径……然而…… 当你看到我这样开篇时，你一定会觉得我马上就要有个转折，告诉你这是不可能的，一切都需要付出和努力……然而，你错了，这篇文章还真就是一篇“秘籍”，只要你把这些“秘籍”用起来，你就一定可以超过大多数人。而且，这篇文章只有我这个“人生导师”可以写得好。毕竟，我的生命过到了十六进制2B的年纪，踏入这个社会已超过20年，舍我其谁呢？！</p><p>P.S. 这篇文章借鉴于《<a href="https://coolshell.cn/articles/4758.html">如何写出无法维护的代码</a>》一文的风格……嘿嘿</p><h4 id="相关技巧和最佳实践"><a href="#相关技巧和最佳实践" class="headerlink" title="相关技巧和最佳实践"></a>相关技巧和最佳实践</h4><p>要超过别人其实还是比较简单的，尤其在今天的中国，更是简单。因为，你只看看中国的互联网，你就会发现，他们基本上全部都是在消费大众，让大众变得更为地愚蠢和傻瓜。<strong>所以，在今天的中国，你基本上不用做什么，只需要不使用中国互联网，你就很自然地超过大多数人了</strong>。当然，如果你还想跟他们彻底拉开，甩他们几个身位，把别人打到底层，下面的这些“技巧”你要多多了解一下。</p><p>在信息获取上，你要不断地向大众鼓吹下面的这些事：</p><ul><li>让大家都用百度搜索引擎查找信息，订阅微信公众号或是到知乎上学习知识……要做到这一步，你就需要把“百度一下”挂在嘴边，然后要经常在群或朋友圈中转发微信公众号的文章，并且转发知乎里的各种“如何看待……”这样的文章，让他们爱上八卦，爱上转发，爱上碎片。</li><li>让大家到微博或是知识星球上粉一些大咖，密切关注他们的言论和动向……是的，告诉大家，大咖的任何想法一言一行都可以在微博、朋友圈或是知识星球上获得，让大家相信，你的成长和大咖的见闻和闲扯非常有关系，你跟牛人在一个圈子里你也会变牛。</li><li>把今日头条和抖音这样的APP推荐给大家……你只需要让你有朋友成功地安装这两个APP，他们就会花大量的时间在上面，而不能自拔，要让他们安装其实还是很容易的，你要不信你就装一个试玩一会看看（嘿嘿嘿）。</li><li>让大家热爱八卦，八卦并不一定是明星的八卦，还可以是你身边的人，比如，公司的同事，自己的同学，职场见闻，社会热点，争议话题，……这些东西总有一些东西会让人心态有很多微妙的变化，甚至花大量的时间去搜索和阅读大量的观点，以及花大量时间与人辩论争论，这个过程会让人上瘾，让人欲罢不能，然而这些事却和自己没有半毛钱关系。你要做的事就是转发其中一些SB或是很极端的观点，造成大家的一睦讨论后，就早早离场……</li><li>利用爱国主义，让大家觉得不用学英文，不要出国，不要翻墙，咱们已经是强国了……这点其实还是很容易做到的，因为学习是比较逆人性的，所以，只要你鼓吹那些英文无用论，出国活得更惨，国家和民族都变得很强大，就算自己过得很底层，也有大国人民的感觉。</li></ul><p>然后，在知识学习和技能训练上，让他们不得要领并产生幻觉</p><ul><li>让他们混淆认识和知识，以为开阔认知就是学习，让他们有学习和成长的幻觉……</li><li>培养他们要学会使用碎片时间学习。等他们习惯利用碎片时间吃快餐后，他们就会失去精读一本书的耐性……</li><li>不断地给他们各种各样“有价值的学习资料”，让他们抓不住重点，成为一个微信公众号或电子书“收藏家”……</li><li>让他们看一些枯燥无味的基础知识和硬核知识，这样让他们只会用“死记硬背”的方式来学习，甚至直接让他们失去信心，直接放弃……</li><li>玩具手枪是易用的，重武器是难以操控的，多给他们一些玩具，这样他们就会对玩具玩地得心应手，觉得玩玩具就是自己的专业……</li><li>让他们喜欢直接得到答案的工作和学习方式，成为一个伸手党，从此学习再也不思考……</li><li>告诉他们东西做出来就好了，不要追求做漂亮，做优雅，这样他们就会慢慢地变成劳动密集型……</li><li>让他们觉得自己已经很努力了，剩下的就是运气，并说服他们去‘及时行乐’，然后再也找不到高阶和高效率学习的感觉……</li><li>让他们觉得“读完书”、“读过书”就行了，不需要对书中的东西进行思考，进行总结，或是实践，只要囫囵吞枣尽快读完就等同于学好了……</li></ul><p>最后，在认知和格局上，彻底打垮他们，让他们变成韭菜。</p><ul><li>让他们尽可能地用拼命和加班，尽可能的996，并告诉他们这就是通往成功的唯一路径。这样一来，他们必然会被永远困在低端成为最低的劳动力。</li><li>让他们不要看到大的形势，只看到眼前的一亩三分地，做好一个井底之蛙。其实这很简单，就是不要告诉他还有另外一种活法，不要扩大他的认识……</li><li>宣扬一夜暴富以及快速挣钱的案例，最好让他们进入“赌博类”或是“传销类”的地方，比如：股市、数字货币……要让他们相信各种财富神话，相信他们就是那个幸运儿，他们也可以成为巴菲特，可以成为马云……</li><li>告诉他们，一些看上去很难的事都是有捷径的，比如：21天就能学会机器学习，用区块链就能颠覆以及重构整个世界等等……</li><li>多跟他们讲一些小人物的励志的故事，这样让他们相信，不需要学习高级知识，不需要掌握高级技能，只需要用低等的知识和低级的技能，再加上持续不断拼命重复现有的工作，终有一天就会成功……</li><li>多让他们跟别人比较，人比人不会气死人，但是会让人变得浮躁，变得心急，变得焦虑，当一个人没有办法控制自己的情绪，没有办法让自己静下心来，人会失去耐性和坚持，开始好大喜欢功，开始装逼，开始歪门邪道剑走偏锋……</li><li>让他们到体制内的一些非常稳定的地方工作，这样他们拥有不思进取、怕承担责任、害怕犯错、喜欢偷懒、得过且过的素质……</li><li>让他们到体制外的那些喜欢拼命喜欢加班的地方工作，告诉他们爱拼才会赢，努力加班是一种福报，青春就是用来拼的，让他们喜欢上使蛮力的感觉……</li><li>告诉他们你的行业太累太辛苦，干不到30岁。让他们早点转行，不要耽误人生和青春……</li><li>当他们要做决定的时候，一定要让他们更多的关注自己会失去的东西，而不是会得到的东西。培养他们患得患失心态，让他们认识不到事物真正的价值，失去判断能力……（比如：让他们觉得跟对人拍领导的马屁忠于公司比自我的成长更有价值）</li><li>告诉他们，你现有的技能和知识不用更新，就能过好一辈子，新出来的东西没有生命力的……这样他们就会像我们再也不学习的父辈一样很快就会被时代所抛弃……</li><li>每个人都喜欢在一些自己做不到的事上找理由，这种能力不教就会，比如，事情太多没有时间，因为工作上没有用到，等等，你要做的就是帮他们为他们做不到的事找各种非常合理的理由，比如：没事的，一切都是最好的安排；你得不到的那个事没什么意思；你没有面好主要原因是那个面试官问的问题都是可以上网查得到的知识，而不没有问到你真正的能力上；这些东西学了不用很快会忘了，等有了环境再学也不迟……</li></ul><p><strong>最后友情提示一下，上述的这些“最佳实践”你要小心，是所谓，贩毒的人从来不吸毒，开赌场的人从来不赌博！所以，你要小心别自己也掉进去了！这就是“欲练神功，必先自宫”的道理。</strong></p><h4 id="相关原理和思维模型"><a href="#相关原理和思维模型" class="headerlink" title="相关原理和思维模型"></a>相关原理和思维模型</h4><p>对于上面的这些技巧还有很多很多，你自己也可以发明或是找到很多。所以，我来讲讲这其中的一些原理。</p><p>一般来说，超过别人一般来说就是两个维度：</p><ol><li><strong>在认知、知识和技能上</strong>。这是一个人赖以立足社会的能力（参看《<a href="https://coolshell.cn/articles/4235.html">程序员的荒谬之言还是至理名言？</a>》和《<a href="https://coolshell.cn/articles/2250.html">21天教你学会C++</a>》）</li><li><strong>在领导力上</strong>。所谓领导力就是你跑在别人前面，你得要有比别人更好的能力更高的标准（参看《<a href="https://coolshell.cn/articles/17583.html">技术人员发展之路</a>》）</li></ol><p>首先，我们要明白，人的技能是从认识开始，然后通过学校、培训或是书本把“零碎的认知”转换成“系统的知识”，而有要把知识转换成技能，就需要训练和实践，这样才能完成从：认识 -&gt; 知识 -&gt; 技能 的转换。这个转换过程是需要耗费很多时间和精力的，而且其中还需要有强大的学习能力和动手能力，这条路径上有很多的“关卡”，每道关卡都会过滤掉一大部分人。比如：对于一些比较枯燥的硬核知识来说，90%的人基本上就倒下来，不是因为他们没有智商，而是他们没有耐心。</p><h5 id="认知"><a href="#认知" class="headerlink" title="认知"></a>认知</h5><p>要在认知上超过别人，就要在下面几个方面上做足功夫：</p><p>1）<strong>信息渠道</strong>。试想如果别人的信息源没有你的好，那么，这些看不见信息源的人，只能接触得到二手信息甚至三手信息，只能获得被别人解读过的信息，这些信息被三传两递后必定会有错误和失真，甚至会被传递信息的中间人hack其中的信息（也就是“中间人攻击”），而这些找不出信息源的人，只能“被人喂养”，于是，他们最终会被困在信息的底层，永世不得翻身。（比如：学习C语言，放着原作者K&amp;R的不用，硬要用错误百出谭浩强的书，能有什么好呢？）</p><p>2）<strong>信息质量</strong>。信息质量主要表现在两个方面，一个是信息中的燥音，另一个是信息中的质量等级，我们都知道，在大数据处理中有一句名言，叫 garbage in garbage out，你天天看的都是垃圾，你的思想和认识也只有垃圾。所以，如果你的信息质量并不好的话，你的认知也不会好，而且你还要花大量的时间来进行有价值信息的挖掘和处理。</p><p>3）<strong>信息密度</strong>。优质的信息，密度一般都很大，因为这种信息会逼着你去干这么几件事，a）搜索并学习其关联的知识，b）沉思和反省，c）亲手去推理、验证和实践……一般来说，经验性的文章会比知识性的文章会更有这样的功效。比如，类似于像 Effiective C++&#x2F;Java，设计模式，Unix编程艺术，算法导论等等这样的书就是属于这种密度很大的书，而像<a href="https://medium.com/netflix-techblog">Netflix的官方blog</a>和<a href="https://www.allthingsdistributed.com/">AWS CTO的blog</a>等等地方也会经常有一些这样的文章。</p><h5 id="知识"><a href="#知识" class="headerlink" title="知识"></a>知识</h5><p>要在知识上超过别人，你就需要在下面几个方面上做足功夫：</p><p>1）<strong>知识树（图）</strong>。任何知识，只在点上学习不够的，需要在面上学习，这叫系统地学习，这需要我们去总结并归纳知识树或知识图，一个知识面会有多个知识板块组成，一个板块又有各种知识点，一个知识点会导出另外的知识点，各种知识点又会交叉和依赖起来，学习就是要系统地学习整个知识树（图）。而我们都知道，<strong>对于一棵树来说，“根基”是非常重要的，所以，学好基础知识也是非常重要的，对于一个陌生的地方，有一份地图是非常重要的，没有地图的你只会乱窜，只会迷路、练路、走冤枉路！</strong></p><p>2）<strong>知识缘由</strong>。任何知识都是有缘由的，了解一个知识的来龙去脉和前世今生，会让你对这个知识有非常强的掌握，而不再只是靠记忆去学习。靠记忆去学习是一件非常糟糕的事。而对于一些操作性的知识（不需要了解由来的），我把其叫操作知识，就像一些函数库一样，这样的知识只要学会查文档就好了。<strong>能够知其然，知其所以然的人自然会比识知识到表皮的人段位要高很多。</strong></p><p>3）<strong>方法套路</strong>。学习不是为了找到答案，而是找到方法。就像数学一样，你学的是方法，是解题思路，是套路，会用方程式解题的和不会用方程式解题的在解题效率上不可比较，而在微积分面前，其它的解题方法都变成了渣渣。<strong>你可以看到，掌握高级方法的人比别人的优势有多大，学习的目的就是为了掌握更为高级的方法和解题思路</strong>。</p><h5 id="技能"><a href="#技能" class="headerlink" title="技能"></a>技能</h5><p>要在技能上超过别人，你就需要在下面几个方面做足功夫：</p><p>1）<strong>精益求精</strong>。如果你想拥有专业的技能，你要做不仅仅是拼命地重复一遍又一遍的训练，而是在每一次重复训练时你都要找到更好的方法，总结经验，让新的一遍能够更好，更漂亮，更有效率，否则，用相同的方法重复，那你只不过在搬砖罢了。</p><p>2）<strong>让自己犯错</strong>。犯错是有利于成长的，这是因为出错会让人反思，反思更好的方法，反思更完美的方案，总结教训，寻求更好更完美的过程，是技能升级的最好的方式。尤其是当你在出错后，被人鄙视，被人嘲笑后，你会有更大的动力提升自己，这样的动力才是进步的源动力。当然，千万不要同一个错误重复地犯！</p><p>3）<strong>找高手切磋</strong>。下过棋，打个球的人都知道，你要想提升自己的技艺，你必需找高手切磋，在和高手切磋的过程中你会感受到高手的技能和方法，有时候你会情不自禁地哇地一下，我靠，还可以这么玩！</p><h5 id="领导力"><a href="#领导力" class="headerlink" title="领导力"></a>领导力</h5><p>最后一个是领导力，要有领导力或是影响力这个事并不容易，这跟你的野心有多大，好胜心有多强 ，你愿意付出多少很有关系，因为一个人的领导力跟他的标准很有关系，因为有领导力的人的标准比绝大多数人都要高。</p><p>1）<strong>识别自己的特长和天赋</strong>。首先，每个人DNA都可能或多或少都会有一些比大多数人NB的东西（当然，也可能没有），如果你有了，那么在你过去的人生中就一定会表现出来了，就是那种大家遇到这个事会来请教你的寻求你帮助的现象。那种，别人要非常努力，而且毫不费劲的事。一旦你有了这样的特长或天赋，那你就要大力地扩大你的领先优势，千万不要进到那些会限制你优势的地方。你是一条鱼，你就一定要把别人拉到水里来玩，绝对不要去陆地上跟别人拼，不断地在自己的特长和天赋上扩大自己的领先优势，彻底一骑绝尘。</p><p>2）<strong>识别自己的兴趣和事业</strong>。没有天赋也没有问题，还有兴趣点，都说兴趣是最好的老师，当年，Linus就是在学校里对minx着迷了，于是整出个Linux来，这就是兴趣驱动出的东西，一般来说，兴趣驱动的事总是会比那些被动驱动的更好。但是，这里我想说明一下什么叫“真∙兴趣”，真正的兴趣不是那种三天热度的东西，而是那种，你愿意为之付出一辈子的事，是那种无论有多大困难有多难受你都要死磕的事，这才是“真∙兴趣”，这也就是你的“野心”和“好胜心”所在，其实上升到了你的事业。相信我，绝大多数人只有职业而没有事业的。</p><p>3）<strong>建立高级的习惯和方法</strong>。没有天赋没有野心，也还是可以跟别人拼习惯拼方法的，只要你有一些比较好的习惯和方法，那么你一样可以超过大多数人。对此，在习惯上你要做到比较大多数人更自律，更有计划性，更有目标性，比如，每年学习一门新的语言或技术，并可以参与相关的顶级开源项目，每个月训练一个类算法，掌握一种算法，每周阅读一篇英文论文，并把阅读笔记整理出来……自律的是非常可怕的。除此之外，你还需要在方法上超过别人，你需要满世界的找各种高级的方法，其中包括，思考的方法，学习的方法、时间管理的方法、沟通的方法这类软实力的，还有，解决问题的方法（trouble shooting 和 problem solving），设计的方法，工程的方法，代码的方法等等硬实力的，一开始照猫画虎，时间长了就可能会自己发明或推导新的方法。</p><p>4）<strong>勤奋努力执着坚持</strong>。如果上面三件事你都没有也没有能力，那还有最后一件事了，那就是勤奋努力了，就是所谓的“一万小时定律”了（参看《<a href="https://coolshell.cn/articles/2250.html">21天教你学会C++</a>》中的十年学编程一节），我见过很多不聪明的人，悟性也不够（比如我就是一个），别人学一个东西，一个月就好了，而我需要1年甚至更长，但是很多东西都是死的，只要肯花时间就有一天你会搞懂的，耐不住我坚持十年二十年，聪明的人发明个飞机飞过去了，笨一点的人愚公移山也过得去，因为更多的人是懒人，我不用拼过聪明人，我只用拼过那些懒人就好了。</p><p>好了，就这么多，如果哪天你变得消极和不自信，你要来读读我的这篇文章，子曰：温故而知新。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;\assets\css\APlayer.min.css&quot;&gt;&lt;script src=&quot;\assets\js\APlayer.min.js&quot; cla</summary>
      
    
    
    
    <category term="代码人生" scheme="https://nerkeler.github.io/categories/%E4%BB%A3%E7%A0%81%E4%BA%BA%E7%94%9F/"/>
    
    
    <category term="转载" scheme="https://nerkeler.github.io/tags/%E8%BD%AC%E8%BD%BD/"/>
    
  </entry>
  
  <entry>
    <title>线程的生命周期和常用方法</title>
    <link href="https://nerkeler.github.io/2023/05/19/%E7%BA%BF%E7%A8%8B%E7%9A%84%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%E5%92%8C%E5%B8%B8%E7%94%A8%E6%96%B9%E6%B3%95/"/>
    <id>https://nerkeler.github.io/2023/05/19/%E7%BA%BF%E7%A8%8B%E7%9A%84%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%E5%92%8C%E5%B8%B8%E7%94%A8%E6%96%B9%E6%B3%95/</id>
    <published>2023-05-19T03:14:00.000Z</published>
    <updated>2023-05-19T03:15:32.000Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h1 id="线程的生命周期和常用方法"><a href="#线程的生命周期和常用方法" class="headerlink" title="线程的生命周期和常用方法"></a>线程的生命周期和常用方法</h1><h2 id="生命周期"><a href="#生命周期" class="headerlink" title="生命周期"></a>生命周期</h2><p>根据<code>jdk</code>官方文档，线程状态有以下几种</p><ul><li><a href="https://www.matools.com/file/manual/jdk_api_1.8_google/java/lang/Thread.State.html#NEW"><code>NEW</code></a><br>尚未启动的线程处于此状态。</li><li><a href="https://www.matools.com/file/manual/jdk_api_1.8_google/java/lang/Thread.State.html#RUNNABLE"><code>RUNNABLE</code></a><br>在Java虚拟机中执行的线程处于此状态。</li><li><a href="https://www.matools.com/file/manual/jdk_api_1.8_google/java/lang/Thread.State.html#BLOCKED"><code>BLOCKED</code></a><br>被阻塞等待监视器锁定的线程处于此状态。</li><li><a href="https://www.matools.com/file/manual/jdk_api_1.8_google/java/lang/Thread.State.html#WAITING"><code>WAITING</code></a><br>正在等待另一个线程执行特定动作的线程处于此状态。</li><li><a href="https://www.matools.com/file/manual/jdk_api_1.8_google/java/lang/Thread.State.html#TIMED_WAITING"><code>TIMED_WAITING</code></a><br>正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。</li><li><a href="https://www.matools.com/file/manual/jdk_api_1.8_google/java/lang/Thread.State.html#TERMINATED"><code>TERMINATED</code></a><br>已退出的线程处于此状态。</li></ul><p>一个线程可以在给定时间点处于一个状态。 这些状态是不反映任何操作系统线程状态的虚拟机状态。</p><h3 id="如图示所见"><a href="#如图示所见" class="headerlink" title="如图示所见"></a>如图示所见</h3><p><img src="https://pic2.imgdb.cn/item/6466c5a70d2dde57773e2592.png"></p><h3 id="代码演示"><a href="#代码演示" class="headerlink" title="代码演示"></a>代码演示</h3><h4 id="NEW-x2F-TIMED-WAITING-x2F-TERMINATED"><a href="#NEW-x2F-TIMED-WAITING-x2F-TERMINATED" class="headerlink" title="NEW &#x2F; TIMED_WAITING &#x2F; TERMINATED"></a>NEW &#x2F; TIMED_WAITING &#x2F; TERMINATED</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> ThreadMethod;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.util.concurrent.TimeUnit;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ThreadState</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> InterruptedException &#123;</span><br><span class="line">        <span class="type">Thread</span> <span class="variable">thread</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(<span class="keyword">new</span> <span class="title class_">Runnable</span>() &#123;</span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    TimeUnit.SECONDS.sleep(<span class="number">3</span>);</span><br><span class="line">                &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">                    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(e);</span><br><span class="line">                &#125;</span><br><span class="line">                </span><br><span class="line"></span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">        &#125;);</span><br><span class="line">        System.out.println(<span class="string">&quot;线程状态：&quot;</span>+ thread.getState());</span><br><span class="line">        thread.start();</span><br><span class="line">        Thread.sleep(<span class="number">2000</span>);</span><br><span class="line">        System.out.println(<span class="string">&quot;线程状态：&quot;</span> + thread.getState());</span><br><span class="line">        Thread.sleep(<span class="number">2000</span>);</span><br><span class="line">        System.out.println(<span class="string">&quot;线程状态：&quot;</span> + thread.getState());</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="结果"><a href="#结果" class="headerlink" title="结果"></a>结果</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">线程状态：NEW</span><br><span class="line">线程状态：TIMED_WAITING</span><br><span class="line">线程状态：TERMINATED</span><br><span class="line">线程状态：TERMINATED</span><br></pre></td></tr></table></figure><h4 id="WAITING-x2F-BLOCKED"><a href="#WAITING-x2F-BLOCKED" class="headerlink" title="WAITING &#x2F; BLOCKED"></a>WAITING &#x2F; BLOCKED</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> ThreadMethod;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BlockThreadState</span> <span class="keyword">implements</span> <span class="title class_">Runnable</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Object</span> <span class="variable">lock</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Object</span>();</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> InterruptedException &#123;</span><br><span class="line"></span><br><span class="line">        <span class="type">BlockThreadState</span> <span class="variable">blockThread</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">BlockThreadState</span>();</span><br><span class="line">        <span class="type">Thread</span> <span class="variable">thread</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(blockThread);</span><br><span class="line">        System.out.println(<span class="string">&quot;thread state : &quot;</span> + thread.getState());</span><br><span class="line">        thread.start();</span><br><span class="line">        Thread.sleep(<span class="number">20</span>);</span><br><span class="line">        System.out.println(<span class="string">&quot;thread state : &quot;</span> + thread.getState());</span><br><span class="line">        <span class="keyword">synchronized</span> (lock)&#123;</span><br><span class="line">            lock.notify();</span><br><span class="line">        &#125;</span><br><span class="line">        System.out.println(<span class="string">&quot;thread state : &quot;</span> + thread.getState());</span><br><span class="line">        Thread.sleep(<span class="number">20</span>);</span><br><span class="line">        System.out.println(<span class="string">&quot;thread state : &quot;</span> + thread.getState());</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="keyword">synchronized</span> (lock)&#123;</span><br><span class="line"><span class="comment">// wait状态</span></span><br><span class="line">                lock.wait();</span><br><span class="line">                <span class="comment">// synchronized重新拿到锁 处于block状态</span></span><br><span class="line">                <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">1000000000</span>; i++) &#123;</span><br><span class="line">                    <span class="keyword">continue</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br></pre></td></tr></table></figure><h5 id="结果-1"><a href="#结果-1" class="headerlink" title="结果"></a>结果</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">thread state : NEW</span><br><span class="line">thread state : WAITING</span><br><span class="line">thread state : BLOCKED</span><br><span class="line">thread state : TERMINATED</span><br></pre></td></tr></table></figure><h2 id="常见方法"><a href="#常见方法" class="headerlink" title="常见方法"></a>常见方法</h2><h3 id="wait"><a href="#wait" class="headerlink" title="wait()"></a>wait()</h3><p>wait()方法执行后，会阻塞线程，同时释放锁，如果想要唤醒该线程，则需要以下条件</p><ul><li>另一个线程调用这个对象的notify()方法，且刚好被唤醒的时本线程</li><li>另一个线程调用了这个对象的<code>notifyAll</code>()方法</li><li>过了wait(long timeout) 规定的超时时间，如果传入0就是永久等待</li><li>线程自身调用了interrupt() </li><li></li></ul><p><em><strong>PS： 使用wait()方法时，必须先拥有monitor锁, 也就是说wait方法需要放在同步代码块中执行</strong></em></p><h3 id="notify-x2F-notifyAll"><a href="#notify-x2F-notifyAll" class="headerlink" title="notify&#x2F;notifyAll"></a>notify&#x2F;<code>notifyAll</code></h3><p>notify&#x2F;<code>notifyAll</code>用于唤醒线程，当另一个线程调用wait()进入  <code>waitting</code>状态时，另一个线程调用<code>notifyAll</code>()可唤醒当前线程（如果有多个线程，使用notify并不一定能够唤醒线程）</p><p>组合使用示例</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BlockThread</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">Message</span> <span class="variable">message</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Message</span>();</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 创建一个等待线程</span></span><br><span class="line">        <span class="type">Thread</span> <span class="variable">waitThread</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(<span class="keyword">new</span> <span class="title class_">WaitThread</span>(message));</span><br><span class="line">        <span class="comment">// 创建一个唤醒线程</span></span><br><span class="line">        <span class="type">Thread</span> <span class="variable">notifyThread</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(<span class="keyword">new</span> <span class="title class_">NotifyThread</span>(message));</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 启动等待线程和唤醒线程</span></span><br><span class="line">        waitThread.start();</span><br><span class="line">        notifyThread.start();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 共享的消息类</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Message</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">boolean</span> <span class="variable">isReady</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 等待方法</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title function_">waitForMessage</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">while</span> (!isReady) &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="comment">// 当消息不可用时，线程进入等待状态</span></span><br><span class="line">                System.out.println(<span class="string">&quot;线程进入等待状态&quot;</span>);</span><br><span class="line">                wait();</span><br><span class="line">            &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">                e.printStackTrace();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 执行到这里表示收到消息，进行处理</span></span><br><span class="line">        System.out.println(<span class="string">&quot;收到消息！&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 唤醒方法</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title function_">sendMessage</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">// 做一些准备工作，例如获取消息</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">// 唤醒等待的线程</span></span><br><span class="line">        isReady = <span class="literal">true</span>;</span><br><span class="line">        System.out.println(<span class="string">&quot;唤醒等待的线程&quot;</span>);</span><br><span class="line">        notify();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 等待线程</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">WaitThread</span> <span class="keyword">implements</span> <span class="title class_">Runnable</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> Message message;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">WaitThread</span><span class="params">(Message message)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.message = message;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">// 等待消息</span></span><br><span class="line">        message.waitForMessage();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 唤醒线程</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">NotifyThread</span> <span class="keyword">implements</span> <span class="title class_">Runnable</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> Message message;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">NotifyThread</span><span class="params">(Message message)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.message = message;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">// 发送消息</span></span><br><span class="line">        message.sendMessage();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">线程进入等待状态</span></span><br><span class="line"><span class="comment">唤醒等待的线程</span></span><br><span class="line"><span class="comment">收到消息！</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">Process finished with exit code 0</span></span><br><span class="line"><span class="comment">*/</span></span><br></pre></td></tr></table></figure><h3 id="图示-monitor锁"><a href="#图示-monitor锁" class="headerlink" title="图示 monitor锁"></a>图示 monitor锁</h3><p><img src="https://pic2.imgdb.cn/item/6462384a0d2dde577761578b.png"></p><ul><li>Entry Set 入口集  <ul><li>线程进入后抢锁</li></ul></li><li>The owner 锁持有线程<ul><li>如果方法没有执行完之前没有释放锁，则程序正常退出并释放锁</li><li>如果方法没有执行完之前释放了锁如调用了wait()方法，则程序再次进入等待集进行抢锁</li></ul></li><li>Wait Set 等待集 <ul><li>如果上方入口集，不同点是等待集在执行方法锁时中途释放了锁</li></ul></li></ul><h3 id="wait、notify-实现生产者消费者模式"><a href="#wait、notify-实现生产者消费者模式" class="headerlink" title="wait、notify 实现生产者消费者模式"></a>wait、notify 实现生产者消费者模式</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.util.LinkedList;</span><br><span class="line"><span class="keyword">import</span> java.util.List;</span><br><span class="line"><span class="keyword">import</span> java.util.TreeMap;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ProductAndConsumer</span> &#123;</span><br><span class="line"></span><br><span class="line">    List&lt;Object&gt; container = <span class="keyword">new</span> <span class="title class_">LinkedList</span>&lt;&gt;();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">ProductAndConsumer</span> <span class="variable">productAndConsumer</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ProductAndConsumer</span>();</span><br><span class="line">        <span class="type">Thread</span> <span class="variable">thread1</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(<span class="keyword">new</span> <span class="title class_">Product</span>(productAndConsumer));</span><br><span class="line">        <span class="type">Thread</span> <span class="variable">thread2</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(<span class="keyword">new</span> <span class="title class_">Consumer</span>(productAndConsumer));</span><br><span class="line">        thread1.start();</span><br><span class="line">        thread2.start();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title function_">addObject</span><span class="params">()</span> <span class="keyword">throws</span> InterruptedException &#123;</span><br><span class="line">        <span class="keyword">if</span> (container.size() &gt;=<span class="number">100</span>)&#123;</span><br><span class="line">            wait();</span><br><span class="line">        &#125;</span><br><span class="line">        container.add(<span class="keyword">new</span> <span class="title class_">Object</span>());</span><br><span class="line">        System.out.println(<span class="string">&quot;正在生产第&quot;</span>+ container.size() + <span class="string">&quot;个&quot;</span>);</span><br><span class="line">        notify();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title function_">conObject</span><span class="params">()</span> <span class="keyword">throws</span> InterruptedException &#123;</span><br><span class="line">        <span class="keyword">if</span> (container.size() == <span class="number">0</span>)&#123;</span><br><span class="line">            wait();</span><br><span class="line">        &#125;</span><br><span class="line">        container.remove(<span class="number">0</span>);</span><br><span class="line">        System.out.println(<span class="string">&quot;正在消费第&quot;</span>+ container.size() + <span class="string">&quot;个&quot;</span>);</span><br><span class="line">        notify();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Product</span> <span class="keyword">implements</span> <span class="title class_">Runnable</span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> ProductAndConsumer productAndConsumer;</span><br><span class="line"></span><br><span class="line">    Product(ProductAndConsumer productAndConsumer)&#123;</span><br><span class="line">        <span class="built_in">this</span>.productAndConsumer = productAndConsumer;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">       <span class="keyword">while</span> (<span class="literal">true</span>)&#123;</span><br><span class="line">           <span class="keyword">try</span> &#123;</span><br><span class="line">               productAndConsumer.addObject();</span><br><span class="line">           &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">               e.printStackTrace();</span><br><span class="line">           &#125;</span><br><span class="line">       &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Consumer</span> <span class="keyword">implements</span> <span class="title class_">Runnable</span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> ProductAndConsumer productAndConsumer;</span><br><span class="line"></span><br><span class="line">    Consumer(ProductAndConsumer productAndConsumer)&#123;</span><br><span class="line">        <span class="built_in">this</span>.productAndConsumer = productAndConsumer;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">       <span class="keyword">while</span> (<span class="literal">true</span>)&#123;</span><br><span class="line">           <span class="keyword">try</span> &#123;</span><br><span class="line">               productAndConsumer.conObject();</span><br><span class="line">           &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">               e.printStackTrace();</span><br><span class="line">           &#125;</span><br><span class="line">       &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="解释"><a href="#解释" class="headerlink" title="解释"></a>解释</h4><ul><li><strong>使用类<code>ProductAndConsumer</code>作为锁对象，container作为容器</strong></li><li><strong>wait notify来进行线程之间的通讯，在数量满足条件时使用wait释放当前锁对象，另一个对象拿到锁之后进行生产或消费</strong></li></ul><h4 id="图示"><a href="#图示" class="headerlink" title="图示"></a>图示</h4><p><img src="https://pic2.imgdb.cn/item/6466d6fd0d2dde577751102f.png"></p><h3 id="sleep"><a href="#sleep" class="headerlink" title="sleep()"></a>sleep()</h3><p><code>Thread.sleep()</code>是Java中一个静态native方法，用于使当前线程进入休眠状态（暂停执行）一段指定的时间。</p><p>它的方法签名为：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">native</span> <span class="keyword">void</span> <span class="title function_">sleep</span><span class="params">(<span class="type">long</span> millis)</span> <span class="keyword">throws</span> InterruptedException;</span><br></pre></td></tr></table></figure><p>参数<code>millis</code>表示线程休眠的时间，以毫秒为单位。传入的值是一个正整数，表示线程要休眠的毫秒数。注意，该方法会抛出<code>InterruptedException</code>异常，因为线程在休眠期间可能被其他线程中断。</p><h4 id="特点和用法"><a href="#特点和用法" class="headerlink" title="特点和用法"></a>特点和用法</h4><ul><li><strong>线程阻塞</strong>：调用<code>Thread.sleep()</code>方法会导致当前线程暂停执行，进入阻塞状态。在指定的时间内，线程不会进行任何操作。</li><li><strong>时间精度</strong>：传入的休眠时间是以毫秒为单位，但实际的休眠时间可能会稍长或稍短。具体的精度取决于底层操作系统和JVM的实现。</li><li><strong>中断响应</strong>：如果在线程休眠期间，另一个线程中断了正在休眠的线程，<code>Thread.sleep()</code>方法会抛出<code>InterruptedException</code>异常。可以在<code>catch</code>块中处理该异常，或者将异常继续向上抛出。</li><li><strong>不会释放锁</strong>：<code>Thread.sleep()</code>方法会暂停当前线程的执行，但不会释放任何锁。如果线程在执行同步代码块或同步方法时调用了<code>Thread.sleep()</code>，其他线程仍无法获得该锁。</li><li><strong>静态方法</strong>：<code>Thread.sleep()</code>是一个静态方法，可以直接通过<code>Thread</code>类调用，无需创建线程对象。</li><li><strong>用途</strong>：常见的用途包括模拟延迟、定时任务、控制线程执行顺序等。</li></ul><h4 id="Thread-sleep和TimeUnit比较"><a href="#Thread-sleep和TimeUnit比较" class="headerlink" title="Thread.sleep和TimeUnit比较"></a><code>Thread.sleep</code>和<code>TimeUnit</code>比较</h4><ul><li><strong>精度和可读性：</strong> <code>Thread.sleep()</code>的参数是以毫秒为单位的时间值，表示线程要休眠的时间。而<code>TimeUnit</code>提供了更高层次的时间单位，如<code>TimeUnit.SECONDS</code>表示秒，<code>TimeUnit.MILLISECONDS</code>表示毫秒等。使用<code>TimeUnit</code>可以使代码更具可读性，而不需要手动计算毫秒数。</li><li><strong>异常处理：</strong> <code>Thread.sleep()</code>方法会抛出<code>InterruptedException</code>异常，因为线程在休眠期间可能会被其他线程中断。而<code>TimeUnit</code>方式不会直接抛出异常，需要开发者手动处理中断情况。</li><li><strong>静态与非静态：</strong> <code>Thread.sleep()</code>是<code>Thread</code>类的静态方法，可以直接通过类名调用。而<code>TimeUnit</code>是一个枚举类，需要通过具体的枚举常量来调用其方法，例如<code>TimeUnit.SECONDS.sleep(1)</code>。</li><li><strong>可读性和易用性：</strong> 使用<code>TimeUnit</code>可以提高代码的可读性，因为可以直观地表示时间单位。此外，<code>TimeUnit</code>还提供了其他方法，如<code>TimeUnit.toMillis()</code>、<code>TimeUnit.toSeconds()</code>等，方便进行时间单位之间的转换。</li></ul><h5 id="TimeUnit源码"><a href="#TimeUnit源码" class="headerlink" title="TimeUnit源码"></a><code>TimeUnit</code>源码</h5><p><em>以下是截取部分源码</em></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">enum</span> <span class="title class_">TimeUnit</span> &#123;</span><br><span class="line">      <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Performs a &#123;<span class="doctag">@link</span> Thread#sleep(long, int) Thread.sleep&#125; using</span></span><br><span class="line"><span class="comment">     * this time unit.</span></span><br><span class="line"><span class="comment">     * This is a convenience method that converts time arguments into the</span></span><br><span class="line"><span class="comment">     * form required by the &#123;<span class="doctag">@code</span> Thread.sleep&#125; method.</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> timeout the minimum time to sleep. If less than</span></span><br><span class="line"><span class="comment">     * or equal to zero, do not sleep at all.</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@throws</span> InterruptedException if interrupted while sleeping</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">sleep</span><span class="params">(<span class="type">long</span> timeout)</span> <span class="keyword">throws</span> InterruptedException &#123;</span><br><span class="line">        <span class="keyword">if</span> (timeout &gt; <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="type">long</span> <span class="variable">ms</span> <span class="operator">=</span> toMillis(timeout);</span><br><span class="line">            <span class="type">int</span> <span class="variable">ns</span> <span class="operator">=</span> excessNanos(timeout, ms);</span><br><span class="line">            Thread.sleep(ms, ns);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可以看到<code>TimeUnit</code>底层还是调用了<code>Thread.sleep()</code> 有一个比较隐含的地方就是当使用过<code>TimeUnit</code>的<code>sleep</code>方法时，如果传入的时间小于是不会进入if判断，而<code>Thread.sleep()</code>方法如果传参小于0则会抛出异常(源码见下)</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">sleep</span><span class="params">(<span class="type">long</span> millis, <span class="type">int</span> nanos)</span></span><br><span class="line"><span class="keyword">throws</span> InterruptedException &#123;</span><br><span class="line">    <span class="keyword">if</span> (millis &lt; <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(<span class="string">&quot;timeout value is negative&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (nanos &lt; <span class="number">0</span> || nanos &gt; <span class="number">999999</span>) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(</span><br><span class="line">                            <span class="string">&quot;nanosecond timeout value out of range&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (nanos &gt;= <span class="number">500000</span> || (nanos != <span class="number">0</span> &amp;&amp; millis == <span class="number">0</span>)) &#123;</span><br><span class="line">        millis++;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    sleep(millis);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="join"><a href="#join" class="headerlink" title="join()"></a>join()</h3><p><code>Thread.join()</code>是Java中的一个方法，用于等待调用该方法的线程执行完毕。它的作用是让当前线程等待指定线程执行结束，然后再继续执行当前线程的后续代码。</p><p>简单来说就是阻塞主线程。</p><h4 id="简单示例"><a href="#简单示例" class="headerlink" title="简单示例"></a>简单示例</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> ThreadMethod;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">JoinMethod</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">Thread</span> <span class="variable">thread1</span> <span class="operator">=</span>  <span class="keyword">new</span> <span class="title class_">Thread</span>(()-&gt;&#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                Thread.sleep(<span class="number">3000</span>);</span><br><span class="line">            &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">                e.printStackTrace();</span><br><span class="line">            &#125;</span><br><span class="line">            System.out.println(<span class="string">&quot;子线程执行完毕&quot;</span>);</span><br><span class="line">        &#125;,<span class="string">&quot;子线程&quot;</span>);</span><br><span class="line">        thread1.start();</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            thread1.join();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">        System.out.println(<span class="string">&quot;主线程执行完毕&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="结果-2"><a href="#结果-2" class="headerlink" title="结果"></a>结果</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">子线程执行完毕</span><br><span class="line">主线程执行完毕</span><br></pre></td></tr></table></figure><p>可以看到子线程等待了3秒，但是最终还是子线程先执行完毕在执行主线程打印，原因是因为thread1.join 对主线程进行了阻塞，这是主线程需要等子线程执行完毕才会执行后面的语句</p><h4 id="特点和注意事项"><a href="#特点和注意事项" class="headerlink" title="特点和注意事项"></a>特点和注意事项</h4><ul><li><strong>等待执行：</strong> 调用<code>join()</code>方法的线程将会等待指定线程执行完毕。如果指定线程已经执行完毕，则<code>join()</code>方法会立即返回。</li><li><strong>阻塞调用线程：</strong> 在调用<code>join()</code>方法期间，当前线程将会被阻塞，暂停执行。只有当指定线程执行完毕后，当前线程才会解除阻塞，继续执行。</li><li><strong>异常处理：</strong> <code>join()</code>方法会抛出<code>InterruptedException</code>异常，因为在等待过程中，当前线程可能会被中断。可以在<code>catch</code>块中处理该异常，或将异常继续向上抛出。</li><li><strong>顺序执行：</strong> 通过使用<code>join()</code>方法，可以控制线程的执行顺序。调用<code>join()</code>方法后，当前线程会等待指定线程执行完毕，然后再继续执行后续代码。</li><li><strong>调用对象：</strong> <code>join()</code>方法是一个实例方法，需要通过线程对象调用。例如，如果<code>thread1</code>是一个<code>Thread</code>对象，可以使用<code>thread1.join()</code>来等待<code>thread1</code>执行完毕。</li></ul><h4 id="源码和底层实现"><a href="#源码和底层实现" class="headerlink" title="源码和底层实现"></a>源码和底层实现</h4><p>以下截取部分<code>Thread.join</code>源码</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 入口</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title function_">join</span><span class="params">()</span> <span class="keyword">throws</span> InterruptedException &#123;</span><br><span class="line">    join(<span class="number">0</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 调用的join(0)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title function_">join</span><span class="params">(<span class="type">long</span> millis)</span></span><br><span class="line">    <span class="keyword">throws</span> InterruptedException &#123;</span><br><span class="line">        <span class="type">long</span> <span class="variable">base</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line">        <span class="type">long</span> <span class="variable">now</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (millis &lt; <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(<span class="string">&quot;timeout value is negative&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (millis == <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="keyword">while</span> (isAlive()) &#123;</span><br><span class="line">                wait(<span class="number">0</span>);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">while</span> (isAlive()) &#123;</span><br><span class="line">                <span class="type">long</span> <span class="variable">delay</span> <span class="operator">=</span> millis - now;</span><br><span class="line">                <span class="keyword">if</span> (delay &lt;= <span class="number">0</span>) &#123;</span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">                &#125;</span><br><span class="line">                wait(delay);</span><br><span class="line">                now = System.currentTimeMillis() - base;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 实际调用的本地native方法  wait</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">native</span> <span class="keyword">void</span> <span class="title function_">wait</span><span class="params">(<span class="type">long</span> timeout)</span> <span class="keyword">throws</span> InterruptedException;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>可以看到join的底层还是使用wait方法实现的，子线程调用wait方法让主线程进入等待状态，在运行结束后自动调用notify方法唤醒主线程。（具体唤醒的方法在jvm中）</p><p><em><strong>有一点疑问我没有找到解释：为什么调用子线程的wait(0)方法，阻塞的确是主线程呢？</strong></em></p><h4 id="CountDownLatch和CyclicBarrier"><a href="#CountDownLatch和CyclicBarrier" class="headerlink" title="CountDownLatch和CyclicBarrier"></a><code>CountDownLatch</code>和<code>CyclicBarrier</code></h4><p>使用<code>countDownLatch</code>和<code>CyclicBarrier</code>也可以实现线程之间的阻塞，具体暂不讨论</p><h3 id="yeild-让步"><a href="#yeild-让步" class="headerlink" title="yeild() (让步)"></a>yeild() (让步)</h3><p><code>yield()</code> 是一个静态方法，它属于 <code>Thread</code> 类，用于提示调度器将当前线程让出 CPU 的执行权，使得其他具有相同优先级的线程有机会执行。</p><p><code>yield()</code> 方法的调用并不能保证一定会使其他线程获得执行机会，它仅是一个提示。具体的调度行为取决于操作系统和 JVM 的实现。因此，在实际应用中，不应过度依赖 <code>yield()</code> 方法来控制线程的执行顺序，而应使用更可靠的线程同步机制来实现需要的线程协作和同步。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;\assets\css\APlayer.min.css&quot;&gt;&lt;script src=&quot;\assets\js\APlayer.min.js&quot; cla</summary>
      
    
    
    
    <category term="Java" scheme="https://nerkeler.github.io/categories/Java/"/>
    
    
    <category term="java" scheme="https://nerkeler.github.io/tags/java/"/>
    
    <category term="多线程" scheme="https://nerkeler.github.io/tags/%E5%A4%9A%E7%BA%BF%E7%A8%8B/"/>
    
  </entry>
  
  <entry>
    <title>如何正确停止线程</title>
    <link href="https://nerkeler.github.io/2023/05/11/%E5%A6%82%E4%BD%95%E6%AD%A3%E7%A1%AE%E5%81%9C%E6%AD%A2%E7%BA%BF%E7%A8%8B/"/>
    <id>https://nerkeler.github.io/2023/05/11/%E5%A6%82%E4%BD%95%E6%AD%A3%E7%A1%AE%E5%81%9C%E6%AD%A2%E7%BA%BF%E7%A8%8B/</id>
    <published>2023-05-11T03:21:00.000Z</published>
    <updated>2025-05-25T07:25:17.317Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h2 id="如何正确停止线程"><a href="#如何正确停止线程" class="headerlink" title="如何正确停止线程"></a>如何正确停止线程</h2><h4 id="新建线程"><a href="#新建线程" class="headerlink" title="新建线程"></a>新建线程</h4><h5 id="新建线程的方式有多种"><a href="#新建线程的方式有多种" class="headerlink" title="新建线程的方式有多种"></a>新建线程的方式有多种</h5><ul><li>实现Runnable接口，重写run()方法</li><li>继承Thread类，重写run()方法</li><li>实现callable接口，重写call方法</li><li>使用线程池</li><li>使用定时器</li><li>…………..</li></ul><h5 id="代码举例-Runnable接口"><a href="#代码举例-Runnable接口" class="headerlink" title="代码举例-Runnable接口"></a>代码举例-Runnable接口</h5><p>Runnable接口重写run方法</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">NewRunnable</span> <span class="keyword">implements</span> <span class="title class_">Runnable</span>&#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;I am NewRunnable Thread&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">NewRunnable</span> <span class="variable">newRunnable</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">NewRunnable</span>();</span><br><span class="line">        <span class="type">Thread</span> <span class="variable">thread</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(newRunnable);</span><br><span class="line">        thread.start();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 结果</span></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">    I am NewRunnable Thread</span></span><br><span class="line"><span class="comment">*/</span></span><br></pre></td></tr></table></figure><p>上述代码中新建<code>NewRunnable</code> 类实现了<code>Runnable</code>接口后重写了run方法，实现新建一个线程，可能这样体现不出来多线程，可以加个循环。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">NewThread</span>  &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line"></span><br><span class="line">        <span class="type">Thread</span> <span class="variable">thread</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(<span class="keyword">new</span> <span class="title class_">Runnable</span>() &#123;</span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">                <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">5</span>; i++) &#123;</span><br><span class="line">                    <span class="keyword">try</span> &#123;</span><br><span class="line">                        Thread.sleep(<span class="number">200</span>);</span><br><span class="line">                        System.out.println(<span class="string">&quot;i am &quot;</span>+ i);</span><br><span class="line">                    &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">                        e.printStackTrace();</span><br><span class="line">                    &#125;</span><br><span class="line"></span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line">        thread.start();</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            Thread.sleep(<span class="number">500</span>);</span><br><span class="line">            System.out.println(<span class="string">&quot;i am mainThread&quot;</span>);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><p>运行结果</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">i am <span class="number">0</span></span><br><span class="line">i am <span class="number">1</span></span><br><span class="line">i am mainThread</span><br><span class="line">i am <span class="number">2</span></span><br><span class="line">i am <span class="number">3</span></span><br><span class="line">i am <span class="number">4</span></span><br></pre></td></tr></table></figure><p>使用了匿名内部类重写了Runnable接口run方法，然后循环五次，每次睡眠200毫秒，主线程中睡眠500毫秒，所以当子线程循环两次后，在第三次打印中间主线程开始打印。</p><h5 id="代码举例-Thread类"><a href="#代码举例-Thread类" class="headerlink" title="代码举例-Thread类"></a>代码举例-Thread类</h5><p>继承Thread类</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">NewThread</span> <span class="keyword">extends</span> <span class="title class_">Thread</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;I am NewThread!&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">NewThread</span> <span class="variable">newThread</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">NewThread</span>();</span><br><span class="line">        newThread.start();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">   I am NewThread!</span></span><br><span class="line"><span class="comment">*/</span></span><br></pre></td></tr></table></figure><h5 id="新建线程总结"><a href="#新建线程总结" class="headerlink" title="新建线程总结"></a>新建线程总结</h5><p>无论是实现Runnable接口还是继承Thread类，发现最终都是通过Thread类的start()方法开启的线程，而不是通过run()方法，看一下run方法和start方法的源代码</p><h6 id="run-方法"><a href="#run-方法" class="headerlink" title="run()方法"></a>run()方法</h6><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Thread</span> <span class="keyword">implements</span> <span class="title class_">Runnable</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">Thread</span><span class="params">(Runnable target)</span> &#123;</span><br><span class="line">        init(<span class="literal">null</span>, target, <span class="string">&quot;Thread-&quot;</span> + nextThreadNum(), <span class="number">0</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (target != <span class="literal">null</span>) &#123;</span><br><span class="line">            target.run();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>发现Thread也实现了Runnable接口，并重写了run方法，如上所示，我们在使用实现Runnable接口，并把对象当作参数传给Thread类中 public Thread(Runnable target)；构造器时，这时候run()方法执行的就是传入对象的run()方法，run()方法中也只有这三行代码，所以并没有实现多线程。</p><h6 id="start-方法"><a href="#start-方法" class="headerlink" title="start()方法"></a>start()方法</h6><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title function_">start</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * This method is not invoked for the main method thread or &quot;system&quot;</span></span><br><span class="line"><span class="comment">     * group threads created/set up by the VM. Any new functionality added</span></span><br><span class="line"><span class="comment">     * to this method in the future may have to also be added to the VM.</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * A zero status value corresponds to state &quot;NEW&quot;.</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">if</span> (threadStatus != <span class="number">0</span>)</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalThreadStateException</span>();</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* Notify the group that this thread is about to be started</span></span><br><span class="line"><span class="comment">     * so that it can be added to the group&#x27;s list of threads</span></span><br><span class="line"><span class="comment">     * and the group&#x27;s unstarted count can be decremented. */</span></span><br><span class="line">    group.add(<span class="built_in">this</span>);</span><br><span class="line"></span><br><span class="line">    <span class="type">boolean</span> <span class="variable">started</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        start0();</span><br><span class="line">        started = <span class="literal">true</span>;</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="keyword">if</span> (!started) &#123;</span><br><span class="line">                group.threadStartFailed(<span class="built_in">this</span>);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Throwable ignore) &#123;</span><br><span class="line">            <span class="comment">/* do nothing. If start0 threw a Throwable then</span></span><br><span class="line"><span class="comment">              it will be passed up the call stack */</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可以看到首先run()方法<code>if (threadStatus != 0)</code> 判断线程状态，而后调用了start0方法，而start0方法<code>private native void start0();</code>可以看到是一个native方法，并非是由java实现</p><p>搜索后得知</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">在Java中，Thread类是用于实现多线程编程的类。当我们创建一个新线程并启动它时，实际上是调用了Thread类中的start()方法，该方法会执行一个本地方法start0()。start0()方法会启动一个新的系统线程，并调用run()方法。run()方法是线程执行的代码，当run()方法执行完毕后，线程就会自动结束。</span><br><span class="line"></span><br><span class="line">start0()方法是一个本地方法（<span class="keyword">native</span> method），它是由JVM实现的。本地方法是指使用C/C++等低级语言编写的方法，它们通常由JVM加载并在本地系统上执行。start0()方法在底层实现了线程的创建和启动，其具体实现会依赖于不同的操作系统和JVM实现。在JVM中，start0()方法的实现是由<span class="keyword">native</span>层面提供的，因此我们无法直接查看其源代码。</span><br></pre></td></tr></table></figure><p>所以我们可以得知，在java中真正实现多线程的是Thread类中的start()方法。</p><h5 id="补充-callable接口"><a href="#补充-callable接口" class="headerlink" title="补充 callable接口"></a>补充 callable接口</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">NewCallable</span> <span class="keyword">implements</span> <span class="title class_">Callable</span> &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">call</span><span class="params">()</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">s</span> <span class="operator">=</span> <span class="string">&quot;I am newCallable!&quot;</span>;</span><br><span class="line">        System.out.println(Thread.currentThread().getName());</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> s;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">NewCallable</span> <span class="variable">newCallable</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">NewCallable</span>();</span><br><span class="line"></span><br><span class="line">        <span class="type">FutureTask</span> <span class="variable">futureTask</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">FutureTask</span>&lt;&gt;(newCallable);</span><br><span class="line">        <span class="type">Thread</span> <span class="variable">thread</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(futureTask);</span><br><span class="line">        thread.start();</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="type">Object</span> <span class="variable">o</span> <span class="operator">=</span> futureTask.get();</span><br><span class="line">            System.out.println(o);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (ExecutionException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">        System.out.println(<span class="string">&quot;i am mainThread&quot;</span>);</span><br><span class="line">        System.out.println(Thread.currentThread().getName());</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">    Thread-0</span></span><br><span class="line"><span class="comment">    I am newCallable!</span></span><br><span class="line"><span class="comment">    i am mainThread</span></span><br><span class="line"><span class="comment">    main</span></span><br><span class="line"><span class="comment">*/</span></span><br></pre></td></tr></table></figure><p>可以看到实现callable接口也是通过Thread类创建的新线程，不同的是使用了<code>FutureTask</code>类接收了返回值</p><p>简要说一下<code>futureTask</code>可以看到该类间接继承了Runnable接口，所以依然可以理解成callable接口就是实现<code>Runnbale</code>接口，重写了run方法的方式实现了多线程，其他方式不在过多阐释。</p><p><img src="https://pic2.imgdb.cn/item/645c4b0a0d2dde577754bbae.png"></p><h4 id="停止线程"><a href="#停止线程" class="headerlink" title="停止线程"></a>停止线程</h4><h5 id="如何停止线程？"><a href="#如何停止线程？" class="headerlink" title="如何停止线程？"></a>如何停止线程？</h5><ul><li>stop()?</li><li>destroy()?</li><li>suspend()?</li><li>interrupt()?</li></ul><p>查看 API，我们会看到 <code>java.lang.Thread</code> 类型提供了一系列的方法如 start()、stop()、resume()、suspend()、destroy()等方法来管理线程。但是除了 start() 之外，其它几个方法都被声名为已过时（deprecated）</p><p>详细原因可看<code>jdk</code>文档的解释  </p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">https://docs.oracle.com/javase/<span class="number">1.5</span>.<span class="number">0</span>/docs/guide/misc/threadPrimitiveDeprecation.html</span><br></pre></td></tr></table></figure><p><strong>正确停止线程的方式是使用interrupt()方法</strong></p><h5 id="Interrupt-方法"><a href="#Interrupt-方法" class="headerlink" title="Interrupt()方法"></a>Interrupt()方法</h5><p>在 Java 中，<code>Thread.interrupt()</code> 方法用于中断线程，其作用是设置线程的中断标志位为 <code>true</code>。被中断的线程可以通过检查自身的中断标志位来判断是否被中断，然后执行适当的操作。</p><p>如果线程被阻塞在某些操作上（如等待 I&#x2F;O 操作、<code>sleep()</code> 等），调用 <code>interrupt()</code> 方法会中断该线程的阻塞状态，抛出 <code>InterruptedException</code> 异常，并清除中断标志位。如果线程没有被阻塞，则调用 <code>interrupt()</code> 方法只是设置线程的中断标志位为 <code>true</code>，线程仍然可以继续运行。</p><h5 id="举个例子"><a href="#举个例子" class="headerlink" title="举个例子"></a>举个例子</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyThread</span> <span class="keyword">extends</span> <span class="title class_">Thread</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="keyword">while</span> (!Thread.currentThread().isInterrupted()) &#123;</span><br><span class="line">                System.out.println(<span class="string">&quot;MyThread is running...&quot;</span>);</span><br><span class="line">                Thread.sleep(<span class="number">1000</span>);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            System.out.println(<span class="string">&quot;MyThread is interrupted!&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">MyThread</span> <span class="variable">myThread</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MyThread</span>();</span><br><span class="line">        myThread.start();</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            Thread.sleep(<span class="number">5000</span>);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">        myThread.interrupt();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 结果</span></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">    MyThread is running...</span></span><br><span class="line"><span class="comment">    MyThread is running...</span></span><br><span class="line"><span class="comment">    MyThread is running...</span></span><br><span class="line"><span class="comment">    MyThread is running...</span></span><br><span class="line"><span class="comment">    MyThread is running...</span></span><br><span class="line"><span class="comment">    MyThread is interrupted!</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>新建了一个线程，在主线程中，我们睡眠5S后将新建线程中断，这是新线程捕获中断，并打印了<code>MyThread is interrupted!</code>，</p><h5 id="中断标志位"><a href="#中断标志位" class="headerlink" title="中断标志位"></a>中断标志位</h5><p>上方代码中，如果我们把<code>while (!Thread.currentThread().isInterrupted())</code> 换成 <code>while (true)</code>发现运行结果是一样的，为什么呢？这就牵扯到中断标志位了，如上方所言 <strong>如果线程被阻塞在某些操作上（如等待 I&#x2F;O 操作、<code>sleep()</code> 等），调用 <code>interrupt()</code> 方法会中断该线程的阻塞状态，抛出 <code>InterruptedException</code> 异常，并清除中断标志位。</strong>上方代码中5s中断线程时，线程在sleep状态，这时候程序抛出 <code>InterruptedException</code> 异常，并清除中断标志位。在抛出异常时线程已经停止了。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyThread</span> <span class="keyword">extends</span> <span class="title class_">Thread</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span><span class="number">0</span>;</span><br><span class="line">            <span class="keyword">while</span> (!Thread.currentThread().isInterrupted())&#123;</span><br><span class="line">                System.out.println(<span class="string">&quot;正在打印：&quot;</span>+ ++i);</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">MyThread</span> <span class="variable">myThread</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MyThread</span>();</span><br><span class="line">        myThread.start();</span><br><span class="line">        <span class="type">int</span> <span class="variable">sum</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">1000000</span>; i++) &#123;</span><br><span class="line">            sum += i;</span><br><span class="line">        &#125;</span><br><span class="line">        myThread.interrupt();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// 结果</span></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">正在打印：0</span></span><br><span class="line"><span class="comment">正在打印：1</span></span><br><span class="line"><span class="comment">正在打印：2</span></span><br><span class="line"><span class="comment">正在打印：3</span></span><br><span class="line"><span class="comment">........</span></span><br><span class="line"><span class="comment">正在打印：104</span></span><br><span class="line"><span class="comment">*/</span></span><br></pre></td></tr></table></figure><p>可以看到我们通过判断程序的中断信号来执行代码，当程序被中断时，跳出while循环，新线程运行结束</p><h5 id="循环中使用注意事项"><a href="#循环中使用注意事项" class="headerlink" title="循环中使用注意事项"></a>循环中使用注意事项</h5><p>如果我们将try catch 放在循环中发现程序响应中断后并未停止，这是因为sleep（）清除了中断标志位，所以不能将try catch语句放在循环中，而应该放在循环外。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyThread</span> <span class="keyword">extends</span> <span class="title class_">Thread</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">while</span> (!Thread.currentThread().isInterrupted()) &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                System.out.println(<span class="string">&quot;MyThread is running...&quot;</span>);</span><br><span class="line">                Thread.sleep(<span class="number">500</span>);</span><br><span class="line">            &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">                e.printStackTrace();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">MyThread</span> <span class="variable">myThread</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MyThread</span>();</span><br><span class="line">        myThread.start();</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            Thread.sleep(<span class="number">2000</span>);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">        myThread.interrupt();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>结果</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">MyThread is running...</span></span><br><span class="line"><span class="comment">MyThread is running...</span></span><br><span class="line"><span class="comment">MyThread is running...</span></span><br><span class="line"><span class="comment">MyThread is running...</span></span><br><span class="line"><span class="comment">java.lang.InterruptedException: sleep interrupted</span></span><br><span class="line"><span class="comment">at java.lang.Thread.sleep(Native Method)</span></span><br><span class="line"><span class="comment">at threadToStop.MyThread.run(MyThread.java:8)</span></span><br><span class="line"><span class="comment">MyThread is running...</span></span><br><span class="line"><span class="comment">MyThread is running...</span></span><br><span class="line"><span class="comment">    */</span></span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;\assets\css\APlayer.min.css&quot;&gt;&lt;script src=&quot;\assets\js\APlayer.min.js&quot; cla</summary>
      
    
    
    
    <category term="Java" scheme="https://nerkeler.github.io/categories/Java/"/>
    
    
    <category term="java" scheme="https://nerkeler.github.io/tags/java/"/>
    
    <category term="多线程" scheme="https://nerkeler.github.io/tags/%E5%A4%9A%E7%BA%BF%E7%A8%8B/"/>
    
  </entry>
  
  <entry>
    <title>Python日志模块logging的使用详解</title>
    <link href="https://nerkeler.github.io/2023/05/04/ython%E6%97%A5%E5%BF%97%E6%A8%A1%E5%9D%97logging%E7%9A%84%E4%BD%BF%E7%94%A8%E8%AF%A6%E8%A7%A3/"/>
    <id>https://nerkeler.github.io/2023/05/04/ython%E6%97%A5%E5%BF%97%E6%A8%A1%E5%9D%97logging%E7%9A%84%E4%BD%BF%E7%94%A8%E8%AF%A6%E8%A7%A3/</id>
    <published>2023-05-04T08:28:00.000Z</published>
    <updated>2025-05-25T07:23:54.105Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h1 id="Python日志模块logging的使用详解"><a href="#Python日志模块logging的使用详解" class="headerlink" title="Python日志模块logging的使用详解"></a>Python日志模块logging的使用详解</h1><p><em><strong>PS: 转载自  <a href="https://github.com/HuiDBK/LogSetupDemo">https://github.com/HuiDBK/LogSetupDemo</a></strong></em></p><h2 id="日志"><a href="#日志" class="headerlink" title="日志"></a>日志</h2><blockquote><p>在开发一些大型项目的时候，都会使用日志来记录项目运行时产生的信息，以备出错时定位分析和从日志信息中提取数据统计分析等。在 <code>Python</code> 中使用 <code>logging</code> 内置模块即可对项目进行日志的配置。</p></blockquote><h2 id="logging模块的使用"><a href="#logging模块的使用" class="headerlink" title="logging模块的使用"></a>logging模块的使用</h2><h3 id="简单使用"><a href="#简单使用" class="headerlink" title="简单使用"></a>简单使用</h3><blockquote><p><code>logging</code> 模块提供了一系列便利的函数</p><p>它们分别是 <code>debug(), info(), warning(), error(), critical()</code></p></blockquote><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> logging</span><br><span class="line"></span><br><span class="line">logging.debug(<span class="string">&#x27;debug log test&#x27;</span>)</span><br><span class="line">logging.info(<span class="string">&#x27;info log test&#x27;</span>)</span><br><span class="line">logging.warning(<span class="string">&#x27;warning log test&#x27;</span>)</span><br><span class="line">logging.error(<span class="string">&#x27;error log test&#x27;</span>)</span><br><span class="line">logging.critical(<span class="string">&#x27;critical log test&#x27;</span>)</span><br></pre></td></tr></table></figure><p><strong>输出结果：</strong></p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">WARNING:root:warning log test</span><br><span class="line">ERROR:root:error log test</span><br><span class="line">CRITICAL:root:critical log test</span><br></pre></td></tr></table></figure><p>为什么只输出了 <code>warning</code>， <code>error</code> 和 <code>critical</code> 的结果，因为 <code>logging</code> 模块默认使用 <code>warning</code> 日志级别，就是只有 <code>warning</code> 及以上日志等级才会显示。</p><p>日志等级从高到低，如下所示</p><table><thead><tr><th>级别</th><th>数值</th><th>何时使用</th></tr></thead><tbody><tr><td><code>CRITICAL</code></td><td>50</td><td>严重的错误，表明程序已不能继续执行</td></tr><tr><td><code>ERROR</code></td><td>40</td><td>由于严重的问题，程序的某些功能已经不能正常执行</td></tr><tr><td><code>WARNING</code></td><td>30</td><td>表明有已经或即将发生的意外，程序仍按预期进行</td></tr><tr><td><code>INFO</code></td><td>20</td><td>确认程序按预期运行</td></tr><tr><td><code>DEBUG</code></td><td>10</td><td>细节信息，仅当诊断问题时适用。</td></tr><tr><td><code>NOTSET</code></td><td>0</td><td>无任何等级限制</td></tr></tbody></table><p>我们只要把 <code>logging</code> 的默认日志等级改下就好了</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> logging</span><br><span class="line"></span><br><span class="line"><span class="comment"># 配置日志等级</span></span><br><span class="line">logging.basicConfig(level=logging.DEBUG)</span><br><span class="line"></span><br><span class="line">logging.debug(<span class="string">&#x27;debug log test&#x27;</span>)</span><br><span class="line">logging.info(<span class="string">&#x27;info log test&#x27;</span>)</span><br><span class="line">logging.warning(<span class="string">&#x27;warning log test&#x27;</span>)</span><br><span class="line">logging.error(<span class="string">&#x27;error log test&#x27;</span>)</span><br></pre></td></tr></table></figure><p><strong>输出结果如下：</strong></p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">DEBUG:root:debug log test</span><br><span class="line">INFO:root:info log test</span><br><span class="line">WARNING:root:warning log test</span><br><span class="line">ERROR:root:error log test</span><br></pre></td></tr></table></figure><h3 id="指定日志输出样式"><a href="#指定日志输出样式" class="headerlink" title="指定日志输出样式"></a>指定日志输出样式</h3><p>当然我们还可以指定日志输出格式</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> logging</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># 日志输出样式</span></span><br><span class="line">log_format = <span class="string">&#x27;%(levelname)s %(asctime)s %(filename)s %(lineno)d %(message)s&#x27;</span></span><br><span class="line">logging.basicConfig(<span class="built_in">format</span>=log_format, level=logging.DEBUG)</span><br><span class="line"></span><br><span class="line">logging.debug(<span class="string">&#x27;debug log test&#x27;</span>)</span><br><span class="line">logging.info(<span class="string">&#x27;info log test&#x27;</span>)</span><br><span class="line">logging.warning(<span class="string">&#x27;warning log test&#x27;</span>)</span><br><span class="line">logging.error(<span class="string">&#x27;error log test&#x27;</span>)</span><br><span class="line">logging.critical(<span class="string">&#x27;critical log test&#x27;</span>)</span><br></pre></td></tr></table></figure><p><strong>输出结果如下：</strong></p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">DEBUG <span class="number">2021</span>-05-<span class="number">27</span> <span class="number">00</span>:04:<span class="number">26</span>,<span class="number">327</span> main.py <span class="number">65</span> debug log test</span><br><span class="line">INFO <span class="number">2021</span>-05-<span class="number">27</span> <span class="number">00</span>:04:<span class="number">26</span>,<span class="number">327</span> main.py <span class="number">66</span> info log test</span><br><span class="line">WARNING <span class="number">2021</span>-05-<span class="number">27</span> <span class="number">00</span>:04:<span class="number">26</span>,<span class="number">327</span> main.py <span class="number">67</span> warning log test</span><br><span class="line">ERROR <span class="number">2021</span>-05-<span class="number">27</span> <span class="number">00</span>:04:<span class="number">26</span>,<span class="number">327</span> main.py <span class="number">68</span> error log test</span><br><span class="line">CRITICAL <span class="number">2021</span>-05-<span class="number">27</span> <span class="number">00</span>:04:<span class="number">26</span>,<span class="number">327</span> main.py <span class="number">69</span> critical log test</span><br></pre></td></tr></table></figure><p>其中日志信息格式化输出配置样式说明</p><ul><li><strong>%(levelname)s ，日志等级</strong></li><li><strong>%(asctime)s ，时间</strong></li><li><strong>%(filename)s ，文件名</strong></li><li><strong>%(lineno)d ，行号</strong></li><li><strong>%(message)s，日志信息</strong></li></ul><p>这些配置都是固定，不可随便写，还有好多日志格式化样式，这里只介绍了一些常用的格式配置，大家可以去官网查看更多的格式化配置信息。<a href="https://docs.python.org/zh-cn/3.7/library/logging.html#formatter-objects">https://docs.python.org/zh-cn/3.7/library/logging.html#formatter-objects</a></p><h3 id="日志记录到文件中"><a href="#日志记录到文件中" class="headerlink" title="日志记录到文件中"></a>日志记录到文件中</h3><p>在 <code>logging.basicConfig</code> 中设置 <code>filename</code> 属性即可把日志信息写入文件中</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> logging</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># 日志输出样式</span></span><br><span class="line">log_format = <span class="string">&#x27;%(levelname)s %(asctime)s %(filename)s %(lineno)d %(message)s&#x27;</span></span><br><span class="line">logging.basicConfig(</span><br><span class="line">    filename=<span class="string">&#x27;test.log&#x27;</span>,</span><br><span class="line">    <span class="built_in">format</span>=log_format,</span><br><span class="line">    level=logging.DEBUG</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">logging.debug(<span class="string">&#x27;debug log test&#x27;</span>)</span><br><span class="line">logging.info(<span class="string">&#x27;info log test&#x27;</span>)</span><br><span class="line">logging.warning(<span class="string">&#x27;warning log test&#x27;</span>)</span><br><span class="line">logging.error(<span class="string">&#x27;error log test&#x27;</span>)</span><br><span class="line">logging.critical(<span class="string">&#x27;critical log test&#x27;</span>)</span><br></pre></td></tr></table></figure><p>运行程序后 <code>test.log</code> 如下内容</p><p><a href="https://camo.githubusercontent.com/0ec87edd65f7f606755fb53dce5758bfc8d623f6b913cba1e9f13d244c4530c5/68747470733a2f2f70312d6a75656a696e2e62797465696d672e636f6d2f746f732d636e2d692d6b3375316662706663702f38646262323366363033336434653565626134363138666232313033643264667e74706c762d6b3375316662706663702d77617465726d61726b2e696d616765"><img src="https://camo.githubusercontent.com/0ec87edd65f7f606755fb53dce5758bfc8d623f6b913cba1e9f13d244c4530c5/68747470733a2f2f70312d6a75656a696e2e62797465696d672e636f6d2f746f732d636e2d692d6b3375316662706663702f38646262323366363033336434653565626134363138666232313033643264667e74706c762d6b3375316662706663702d77617465726d61726b2e696d616765" alt="日志信息展示"></a></p><h2 id="自定义日志配置"><a href="#自定义日志配置" class="headerlink" title="自定义日志配置"></a>自定义日志配置</h2><blockquote><p>通常我们在项目中都是自定义一些通用日志配置，然后供项目全局使用。写好这些配置下次要在别的项目使用之间复制粘贴过来修改修改一下即可。来康康是如何配置的。</p></blockquote><h3 id="准备日志配置信息"><a href="#准备日志配置信息" class="headerlink" title="准备日志配置信息"></a>准备日志配置信息</h3><p>配置日志详细信息，需要导入 <code>logging.config</code> 来进行加载日志配置信息</p><p>首先准备日志配置信息字典</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">log_dict</span> <span class="string">=</span> &#123;</span><br><span class="line">    <span class="attr">&#x27;version&#x27;:</span> <span class="number">1</span>,</span><br><span class="line">    <span class="attr">&#x27;disable_existing_loggers&#x27;:</span> <span class="literal">False</span>,  <span class="comment"># 是否禁用已经存在的日志器</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 日志信息格式化输出配置</span></span><br><span class="line">    <span class="attr">&#x27;formatters&#x27;:</span> &#123;</span><br><span class="line"></span><br><span class="line">        <span class="comment"># 简单的日志输出</span></span><br><span class="line">        <span class="attr">&#x27;simple&#x27;:</span> &#123;</span><br><span class="line">            <span class="attr">&#x27;format&#x27;:</span> <span class="string">&#x27;%(levelname)s %(module)s %(lineno)d %(message)s&#x27;</span></span><br><span class="line">        &#125;,</span><br><span class="line"></span><br><span class="line">        <span class="comment"># 详细的日志输出</span></span><br><span class="line">        <span class="attr">&#x27;verbose&#x27;:</span> &#123;</span><br><span class="line">            <span class="attr">&#x27;format&#x27;:</span> <span class="string">&#x27;%(levelname)s %(asctime)s %(filename)s %(lineno)d %(message)s&#x27;</span></span><br><span class="line">        &#125;,</span><br><span class="line">    &#125;,</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 日志信息处理器配置</span></span><br><span class="line">    <span class="attr">&#x27;handlers&#x27;:</span> &#123;</span><br><span class="line">        </span><br><span class="line">        <span class="comment"># 向终端中输出日志</span></span><br><span class="line">        <span class="attr">&#x27;console&#x27;:</span> &#123;</span><br><span class="line">            <span class="attr">&#x27;level&#x27;:</span> <span class="string">&#x27;DEBUG&#x27;</span>,                   <span class="comment"># 处理的日志等级，DEBUG及以上</span></span><br><span class="line">            <span class="attr">&#x27;class&#x27;:</span> <span class="string">&#x27;logging.StreamHandler&#x27;</span>,   <span class="comment"># 日志处理器</span></span><br><span class="line">            <span class="attr">&#x27;formatter&#x27;:</span> <span class="string">&#x27;simple&#x27;</span>               <span class="comment"># 日志格式化配置</span></span><br><span class="line">        &#125;,</span><br><span class="line"></span><br><span class="line">        <span class="comment"># 向文件中输出日志</span></span><br><span class="line">        <span class="attr">&#x27;file&#x27;:</span> &#123;</span><br><span class="line">            <span class="attr">&#x27;level&#x27;:</span> <span class="string">&#x27;INFO&#x27;</span>,                                    <span class="comment"># 处理的日志等级，DEBUG及以上</span></span><br><span class="line">            <span class="attr">&#x27;class&#x27;:</span> <span class="string">&#x27;logging.handlers.RotatingFileHandler&#x27;</span>,    <span class="comment"># 使用文件日志处理器</span></span><br><span class="line">            <span class="attr">&#x27;formatter&#x27;:</span> <span class="string">&#x27;verbose&#x27;</span>,                             <span class="comment"># 日志格式化配置</span></span><br><span class="line">            <span class="attr">&#x27;filename&#x27;:</span> <span class="string">&#x27;./logs/test.log&#x27;</span>,                      <span class="comment"># 日志文件存储位置</span></span><br><span class="line">            <span class="attr">&#x27;maxBytes&#x27;:</span> <span class="number">1024</span> <span class="string">*</span> <span class="number">1024</span>,        <span class="comment"># 每个日志文件最大 10MB, 单位: byte</span></span><br><span class="line">            <span class="attr">&#x27;backupCount&#x27;:</span> <span class="number">20</span>,              <span class="comment"># 如果文件满了, 自动扩充, 最多保留 20 个日志文件</span></span><br><span class="line">            <span class="attr">&#x27;encoding&#x27;:</span> <span class="string">&#x27;utf8&#x27;</span>,</span><br><span class="line">        &#125;,</span><br><span class="line">    &#125;,</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 默认根日志器</span></span><br><span class="line">    <span class="attr">&#x27;root&#x27;:</span> &#123;</span><br><span class="line">        <span class="attr">&#x27;level&#x27;:</span> <span class="string">&#x27;DEBUG&#x27;</span>,           <span class="comment"># 允许接受的日志等级</span></span><br><span class="line">        <span class="attr">&#x27;handlers&#x27;:</span> [<span class="string">&#x27;console&#x27;</span>]     <span class="comment"># 选择日志处理器</span></span><br><span class="line">    &#125;,</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 自定义的日志器</span></span><br><span class="line">    <span class="attr">&#x27;loggers&#x27;:</span> &#123;</span><br><span class="line">        <span class="attr">&#x27;server&#x27;:</span> &#123;</span><br><span class="line">            <span class="attr">&#x27;level&#x27;:</span> <span class="string">&#x27;DEBUG&#x27;</span>,</span><br><span class="line">            <span class="attr">&#x27;handlers&#x27;:</span> [<span class="string">&#x27;file&#x27;</span>],</span><br><span class="line">            <span class="attr">&#x27;propagate&#x27;:</span> <span class="literal">True</span>       <span class="comment"># 设为 False则禁止将日志消息传递给父级记录器的处理程序中</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>其中大字典的 <code>key</code> 都是固定，例如 <code>version,formatters, handlers, root, loggers</code>等都是固定的配置项。而有一些子选项是可以自己自定义如</p><ul><li><code>formatters</code> 下的 <code>simple</code> 和 <code>verbose</code>，是可以改成自己想要的名字。</li><li><code>handlers</code> 下的 <code>console</code> 和 <code>file</code> 也是可以修改的。</li><li><code>loggers</code> 下的 <code>server</code> 都是一样可以修改的</li></ul><p>具体配置的说明，在这字典中都有一一注释我就不全介绍了，我就介绍一下 <code>handlers</code> 日志处理器的配置</p><p>在 <code>logging</code> 模块中有许多 <strong>日志处理器类</strong>，我们只需要在 <code>pycharm</code> 中输入 <code>logging.Handler</code> 就能弹出最基本的几个日志处理类。</p><p><a href="https://camo.githubusercontent.com/18888f035e50185a528db0d5c84d423b56415cea1c014f4fe73cc81834a07b5a/68747470733a2f2f70362d6a75656a696e2e62797465696d672e636f6d2f746f732d636e2d692d6b3375316662706663702f65306632643238303531316434363965616663636563663564643934363531317e74706c762d6b3375316662706663702d77617465726d61726b2e696d616765"><img src="https://camo.githubusercontent.com/18888f035e50185a528db0d5c84d423b56415cea1c014f4fe73cc81834a07b5a/68747470733a2f2f70362d6a75656a696e2e62797465696d672e636f6d2f746f732d636e2d692d6b3375316662706663702f65306632643238303531316434363965616663636563663564643934363531317e74706c762d6b3375316662706663702d77617465726d61726b2e696d616765" alt="日志处理器"></a></p><p>而上文所用到的 <code>StreamHandler</code> 则是流处理器，日志将随着系统标准输入、输出流展示，而我们的 <strong>PyCharm终端、控制台等</strong> 显示的信息就属于系统标准输出流。</p><p>而 <code>RotatingFileHandler</code> 日志处理器则是 <code>FileHandler</code> 的子类。其主要作用就是把日志写入文件中，当文件内容达到最大限制时可以自动扩充日志文件，以达到日志文件的轮换。</p><h3 id="加载日志配置信息"><a href="#加载日志配置信息" class="headerlink" title="加载日志配置信息"></a>加载日志配置信息</h3><p>然后使用 <code>logging.config.dictConfig()</code> 方法加载日志配置，该方法接受一个 <strong>字典</strong> 参数。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#!/usr/bin/python3</span></span><br><span class="line"><span class="comment"># -*- coding: utf-8 -*-</span></span><br><span class="line"><span class="comment"># @Author: Hui</span></span><br><span class="line"><span class="comment"># @Desc: &#123; 日志模块logging的使用 &#125;</span></span><br><span class="line"><span class="comment"># @Date: 2021/05/26 23:14</span></span><br><span class="line"><span class="keyword">import</span> logging</span><br><span class="line"><span class="keyword">import</span> logging.config</span><br><span class="line"></span><br><span class="line">log_dict = &#123;</span><br><span class="line">    <span class="string">&#x27;version&#x27;</span>: <span class="number">1</span>,</span><br><span class="line">    <span class="string">&#x27;disable_existing_loggers&#x27;</span>: <span class="literal">False</span>,  <span class="comment"># 是否禁用已经存在的日志器</span></span><br><span class="line"></span><br><span class="line">    ...与上文一致故省略</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 默认根日志器</span></span><br><span class="line">    <span class="string">&#x27;root&#x27;</span>: &#123;</span><br><span class="line">        <span class="string">&#x27;level&#x27;</span>: <span class="string">&#x27;DEBUG&#x27;</span>,  <span class="comment"># 接受的日志等级</span></span><br><span class="line">        <span class="string">&#x27;handlers&#x27;</span>: [<span class="string">&#x27;console&#x27;</span>]</span><br><span class="line">    &#125;,</span><br><span class="line">    </span><br><span class="line">    <span class="comment"># 自定义的日志器</span></span><br><span class="line">    <span class="string">&#x27;loggers&#x27;</span>: &#123;</span><br><span class="line">        <span class="string">&#x27;server&#x27;</span>: &#123;</span><br><span class="line">            <span class="string">&#x27;level&#x27;</span>: <span class="string">&#x27;DEBUG&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;handlers&#x27;</span>: [<span class="string">&#x27;file&#x27;</span>],</span><br><span class="line">            <span class="string">&#x27;propagate&#x27;</span>: <span class="literal">True</span>       <span class="comment"># 设为 False则禁止将日志消息传递给父级记录器的处理程序中</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">setup_logging</span>():</span><br><span class="line">    <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">    配置日志信息</span></span><br><span class="line"><span class="string">    :return:</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line">    logging.config.dictConfig(config=log_dict)</span><br><span class="line">    logger = logging.getLogger()</span><br><span class="line"><span class="comment"># logger = logging.getLogger(&#x27;root&#x27;)</span></span><br><span class="line">    </span><br><span class="line">    logger.debug(<span class="string">&#x27;debug log test&#x27;</span>)</span><br><span class="line">    logger.info(<span class="string">&#x27;info log test&#x27;</span>)</span><br><span class="line">    logger.warning(<span class="string">&#x27;warning log test&#x27;</span>)</span><br><span class="line">    logger.error(<span class="string">&#x27;error log test&#x27;</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">main</span>():</span><br><span class="line">    setup_logging()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&#x27;__main__&#x27;</span>:</span><br><span class="line">    main()</span><br></pre></td></tr></table></figure><p>使用 <code>logging.getLogger()</code> 即可获取相应配置日志器，其接受一个日志器的名字，不传则默认使用 <code>root</code> 根日志器，同 <code>logging.getLogger(&#39;root&#39;)</code> 效果一致。</p><p>如果之间运行程序会出现如下错误</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ValueError: Unable to configure handler &#x27;file&#x27;</span><br></pre></td></tr></table></figure><p>那是因为你在日志配置中设置了一个文件处理器 <code>file</code> ，其日志文件将存储在 <code>filename</code> 配置项中，在这里是</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">./logs/test.log# 代表存储在当前路径下的 logs目录下的 test.log 文件</span><br></pre></td></tr></table></figure><p><code>logging</code> 模块不会自动帮我们创建目录，因此只需在当前目录中创建一个 <code>logs</code> 目录即可。</p><p><strong>最后程序运行结果如下</strong></p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">DEBUG main <span class="number">74</span> debug log test</span><br><span class="line">INFO main <span class="number">75</span> info log test</span><br><span class="line">WARNING main <span class="number">76</span> warning log test</span><br><span class="line">ERROR main <span class="number">77</span> error log test</span><br></pre></td></tr></table></figure><p>不用跟 <code>root</code> 根日志器，使用 <code>server</code> 日志器，代码如下</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> logging</span><br><span class="line"><span class="keyword">import</span> logging.config</span><br><span class="line"></span><br><span class="line">log_dict = &#123;...同上文省略...&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">setup_logging</span>():</span><br><span class="line">    <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">    配置日志信息</span></span><br><span class="line"><span class="string">    :return:</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line">    logging.config.dictConfig(config=log_dict)</span><br><span class="line">    <span class="comment"># logger = logging.getLogger()</span></span><br><span class="line"></span><br><span class="line">    logger = logging.getLogger(<span class="string">&#x27;server&#x27;</span>)</span><br><span class="line">    logger.debug(<span class="string">&#x27;debug log test&#x27;</span>)</span><br><span class="line">    logger.info(<span class="string">&#x27;info log test&#x27;</span>)</span><br><span class="line">    logger.warning(<span class="string">&#x27;warning log test&#x27;</span>)</span><br><span class="line">    logger.error(<span class="string">&#x27;error log test&#x27;</span>)</span><br><span class="line">    </span><br><span class="line">    </span><br><span class="line"><span class="keyword">def</span> <span class="title function_">main</span>():</span><br><span class="line">    setup_logging()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&#x27;__main__&#x27;</span>:</span><br><span class="line">    main()    </span><br></pre></td></tr></table></figure><p><strong>运行结果如下：</strong></p><p>控制台</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">DEBUG main <span class="number">75</span> debug log test</span><br><span class="line">INFO main <span class="number">76</span> info log test</span><br><span class="line">WARNING main <span class="number">77</span> warning log test</span><br><span class="line">ERROR main <span class="number">78</span> error log test</span><br></pre></td></tr></table></figure><p>日志文件 <code>logs/test.log</code></p><p><a href="https://camo.githubusercontent.com/95cd1e913e93ec90f1d6666131732faed0d27008fc37f04e070b5c2bb074e98b/68747470733a2f2f70362d6a75656a696e2e62797465696d672e636f6d2f746f732d636e2d692d6b3375316662706663702f39663435353663633439633734353664623264373438363765626262393238657e74706c762d6b3375316662706663702d77617465726d61726b2e696d616765"><img src="https://camo.githubusercontent.com/95cd1e913e93ec90f1d6666131732faed0d27008fc37f04e070b5c2bb074e98b/68747470733a2f2f70362d6a75656a696e2e62797465696d672e636f6d2f746f732d636e2d692d6b3375316662706663702f39663435353663633439633734353664623264373438363765626262393238657e74706c762d6b3375316662706663702d77617465726d61726b2e696d616765" alt="文件日志信息"></a></p><p>由于 <code>server</code> 日志器设置了 <code>&#39;propagate&#39;: True</code>，会 将日志消息传递给父级记录器的处理程序中，因此不仅控制台会显示日志信息，文件也会记录，但文件记录的等级被设置成 <code>INFO</code> 了，因此 <code>DEBUG</code> 调试日志信息，将不会出现在文件中。</p><h2 id="使用日志配置文件"><a href="#使用日志配置文件" class="headerlink" title="使用日志配置文件"></a>使用日志配置文件</h2><blockquote><p>这里我将采用 <code>yaml</code> 格式的日志配置文件。具体配置内容和上文大致一样，多了一个 <code>error_file_handler</code> 错误日志处理器，目的就是把 <strong>错误日志单独放在一个文件中，方便以后排查错误</strong>。</p></blockquote><h3 id="创建日志配置文件"><a href="#创建日志配置文件" class="headerlink" title="创建日志配置文件"></a>创建日志配置文件</h3><p>创建 <code>logging.yaml</code> 文件，内容如下所示</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">version:</span> <span class="number">1</span></span><br><span class="line"><span class="attr">disable_existing_loggers:</span> <span class="literal">true</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 日志信息格式化输出配置</span></span><br><span class="line"><span class="attr">formatters:</span></span><br><span class="line">    <span class="attr">simple:</span></span><br><span class="line">        <span class="attr">format:</span> <span class="string">&#x27;%(levelname)s %(filename)s %(lineno)d %(message)s&#x27;</span></span><br><span class="line">    <span class="attr">verbose:</span></span><br><span class="line">        <span class="attr">format:</span> <span class="string">&#x27;%(levelname)s %(asctime)s -Loc %(filename)s -Row %(lineno)d -%(name)s %(message)s&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 日志信息处理器配置</span></span><br><span class="line"><span class="attr">handlers:</span></span><br><span class="line">    <span class="attr">console:</span></span><br><span class="line">        <span class="attr">class:</span> <span class="string">logging.StreamHandler</span></span><br><span class="line">        <span class="attr">level:</span> <span class="string">DEBUG</span></span><br><span class="line">        <span class="attr">formatter:</span> <span class="string">simple</span></span><br><span class="line">        <span class="attr">stream:</span> <span class="string">ext://sys.stdout</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># 错误日志单独处理</span></span><br><span class="line">    <span class="attr">error_file_handler:</span></span><br><span class="line">        <span class="attr">class:</span> <span class="string">logging.handlers.RotatingFileHandler</span></span><br><span class="line">        <span class="attr">level:</span> <span class="string">ERROR</span></span><br><span class="line">        <span class="attr">formatter:</span> <span class="string">verbose</span></span><br><span class="line">        <span class="attr">filename:</span> <span class="string">./logs/errors.log</span>   <span class="comment"># 错误日志文件存储位置</span></span><br><span class="line">        <span class="attr">maxBytes:</span> <span class="number">10485760</span>            <span class="comment"># 每个日志文件最大 10MB</span></span><br><span class="line">        <span class="attr">backupCount:</span> <span class="number">20</span>               <span class="comment"># 如果文件满了, 自动扩充, 最多保留 20 个日志文件</span></span><br><span class="line">        <span class="attr">encoding:</span> <span class="string">utf8</span></span><br><span class="line"></span><br><span class="line">    <span class="attr">server_file_handler:</span></span><br><span class="line">      <span class="attr">class:</span> <span class="string">logging.handlers.RotatingFileHandler</span></span><br><span class="line">      <span class="attr">level:</span> <span class="string">INFO</span>                     <span class="comment"># 只在文件中记录INFO级别及以上的log</span></span><br><span class="line">      <span class="attr">formatter:</span> <span class="string">verbose</span></span><br><span class="line">      <span class="attr">filename:</span> <span class="string">./logs/server.log</span>    <span class="comment"># 项目日志文件, 记录所有日志信息</span></span><br><span class="line">      <span class="attr">maxBytes:</span> <span class="number">10485760</span>             <span class="comment"># 10MB</span></span><br><span class="line">      <span class="attr">backupCount:</span> <span class="number">30</span></span><br><span class="line">      <span class="attr">encoding:</span> <span class="string">utf8</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 根日志器</span></span><br><span class="line"><span class="attr">root:</span></span><br><span class="line">    <span class="attr">level:</span> <span class="string">DEBUG</span></span><br><span class="line">    <span class="attr">handlers:</span> [<span class="string">console</span>]</span><br><span class="line"></span><br><span class="line"><span class="comment"># 日志器</span></span><br><span class="line"><span class="attr">loggers:</span></span><br><span class="line">    <span class="attr">server:</span></span><br><span class="line">        <span class="attr">level:</span> <span class="string">DEBUG</span>      <span class="comment"># 允许打印 DEBUG 及以上log</span></span><br><span class="line">        <span class="attr">handlers:</span> [<span class="string">server_file_handler</span>, <span class="string">error_file_handler</span>]</span><br><span class="line">        <span class="attr">propagate:</span> <span class="literal">True</span>   <span class="comment"># 设为 False则禁止将日志消息传递给父级记录器的处理程序中</span></span><br></pre></td></tr></table></figure><h3 id="加载日志配置函数"><a href="#加载日志配置函数" class="headerlink" title="加载日志配置函数"></a>加载日志配置函数</h3><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># log_test.py 文件</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> os</span><br><span class="line"><span class="keyword">import</span> yaml</span><br><span class="line"><span class="keyword">import</span> logging</span><br><span class="line"><span class="keyword">import</span> coloredlogs</span><br><span class="line"><span class="keyword">import</span> logging.config</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># 项目根路径</span></span><br><span class="line">BASE_DIR = os.path.dirname(os.path.abspath(__file__))</span><br><span class="line"></span><br><span class="line"><span class="comment"># 日志配置文件</span></span><br><span class="line">LOG_CONF_FILE = os.path.join(BASE_DIR, <span class="string">&#x27;logging.yaml&#x27;</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">setup_logging</span>(<span class="params">default_path=LOG_CONF_FILE, default_level=logging.DEBUG, env_key=<span class="string">&#x27;LOG_CFG&#x27;</span></span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">    配置项目日志信息</span></span><br><span class="line"><span class="string">    :param default_path: 日志文件默认路径</span></span><br><span class="line"><span class="string">    :param default_level: 日志默认等级</span></span><br><span class="line"><span class="string">    :param env_key: 系统环境变量名</span></span><br><span class="line"><span class="string">    :return:</span></span><br><span class="line"><span class="string">    &quot;&quot;&quot;</span></span><br><span class="line">    path = default_path</span><br><span class="line"></span><br><span class="line">    value = os.getenv(env_key, <span class="literal">None</span>)  <span class="comment"># 获取对应的环境变量值</span></span><br><span class="line">    <span class="keyword">if</span> value <span class="keyword">is</span> <span class="keyword">not</span> <span class="literal">None</span>:</span><br><span class="line">        path = value</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> os.path.exists(path):</span><br><span class="line">        <span class="keyword">with</span> <span class="built_in">open</span>(path, mode=<span class="string">&#x27;r&#x27;</span>, encoding=<span class="string">&#x27;utf-8&#x27;</span>) <span class="keyword">as</span> f:</span><br><span class="line">            <span class="keyword">try</span>:</span><br><span class="line">                logging_yaml = yaml.safe_load(f.read())</span><br><span class="line">                logging.config.dictConfig(logging_yaml)</span><br><span class="line">                coloredlogs.install(level=<span class="string">&#x27;DEBUG&#x27;</span>)</span><br><span class="line">            <span class="keyword">except</span> Exception <span class="keyword">as</span> e:</span><br><span class="line">                <span class="built_in">print</span>(e)</span><br><span class="line">                <span class="built_in">print</span>(<span class="string">&#x27;无法加载日志配置文件, 请检查日志目录是否创建, 使用默认的日志配置&#x27;</span>)</span><br><span class="line">                logging.basicConfig(level=default_level)</span><br><span class="line">                coloredlogs.install(level=default_level)</span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        logging.basicConfig(level=default_level)</span><br><span class="line">        coloredlogs.install(level=default_level)</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">&#x27;日志配置文件不存在, 使用默认的日志配置&#x27;</span>)</span><br></pre></td></tr></table></figure><p>这里使用到第三方库如下</p><ul><li><code>yaml</code> 是用于读取 <code>yaml</code> 格式的日志配置文件</li><li><code>coloredlogs</code> 用于让日志在控制台中有颜色显示。</li></ul><p>然后我们在项目中只要执行完 <code>setup_logging()</code> 日志配置函数</p><p>其他模块直接使用 <code>logging.getLogger(&#39;server&#39;)</code> 就可获取我们配置好的日志器。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># log_demo.py 文件</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> logging</span><br><span class="line"></span><br><span class="line">logger = logging.getLogger(<span class="string">&#x27;server&#x27;</span>)  <span class="comment"># 维护一个全局日志对象</span></span><br><span class="line"></span><br><span class="line">logger.debug(<span class="string">&#x27;debug log test&#x27;</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">log_test1</span>():</span><br><span class="line">    logger.info(<span class="string">&#x27;info log test&#x27;</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">log_test2</span>():</span><br><span class="line">    <span class="keyword">try</span>:</span><br><span class="line">        a = <span class="number">1</span> / <span class="number">0</span></span><br><span class="line">    <span class="keyword">except</span> Exception <span class="keyword">as</span> e:</span><br><span class="line">        logger.error(e)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">LogDemo</span>(<span class="title class_ inherited__">object</span>):</span><br><span class="line"></span><br><span class="line"><span class="meta">    @staticmethod</span></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">log_test</span>():</span><br><span class="line">        logger.warning(<span class="string">&#x27;warning log test&#x27;</span>)</span><br></pre></td></tr></table></figure><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># log_test.py</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">main</span>():</span><br><span class="line">    setup_logging()</span><br><span class="line"></span><br><span class="line">    logger = logging.getLogger(<span class="string">&#x27;server&#x27;</span>)</span><br><span class="line">    logger.debug(<span class="string">&#x27;debug log test&#x27;</span>)</span><br><span class="line">    logger.info(<span class="string">&#x27;info log test&#x27;</span>)</span><br><span class="line">    logger.warning(<span class="string">&#x27;warning log test&#x27;</span>)</span><br><span class="line">    logger.error(<span class="string">&#x27;error log test&#x27;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 日志在其他模块中使用演示</span></span><br><span class="line">    <span class="keyword">import</span> log_demo</span><br><span class="line">    log_demo.log_test1()</span><br><span class="line">    log_demo.log_test2()</span><br><span class="line">    log_demo.LogDemo.log_test()</span><br><span class="line">    </span><br><span class="line">    </span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&#x27;__main__&#x27;</span>:</span><br><span class="line">    main()    </span><br></pre></td></tr></table></figure><h3 id="日志效果展示"><a href="#日志效果展示" class="headerlink" title="日志效果展示"></a>日志效果展示</h3><p>运行 <code>log_test.py</code> 结果如下</p><p><strong>控制台信息</strong></p><p><a href="https://camo.githubusercontent.com/ae64f35e9b97106fedf6d0117d57b0638b9285b0bdcecb9f6195f17ce2531e72/68747470733a2f2f70362d6a75656a696e2e62797465696d672e636f6d2f746f732d636e2d692d6b3375316662706663702f31663436653538633739333734343830393863316433333766636163633932667e74706c762d6b3375316662706663702d77617465726d61726b2e696d616765"><img src="https://camo.githubusercontent.com/ae64f35e9b97106fedf6d0117d57b0638b9285b0bdcecb9f6195f17ce2531e72/68747470733a2f2f70362d6a75656a696e2e62797465696d672e636f6d2f746f732d636e2d692d6b3375316662706663702f31663436653538633739333734343830393863316433333766636163633932667e74706c762d6b3375316662706663702d77617465726d61726b2e696d616765" alt="带颜色的日志信息"></a></p><p><strong>全部日志配置文件信息</strong></p><p><a href="https://camo.githubusercontent.com/70d45751490fe41de35c4d6ab10a29f47ff9b20e203dfe89a8463fea0f14636a/68747470733a2f2f70312d6a75656a696e2e62797465696d672e636f6d2f746f732d636e2d692d6b3375316662706663702f34653139326435656530333234633536383037346564336561626361656337337e74706c762d6b3375316662706663702d77617465726d61726b2e696d616765"><img src="https://camo.githubusercontent.com/70d45751490fe41de35c4d6ab10a29f47ff9b20e203dfe89a8463fea0f14636a/68747470733a2f2f70312d6a75656a696e2e62797465696d672e636f6d2f746f732d636e2d692d6b3375316662706663702f34653139326435656530333234633536383037346564336561626361656337337e74706c762d6b3375316662706663702d77617465726d61726b2e696d616765" alt="全部日志文件信息"></a></p><p><strong>错误日志文件信息</strong></p><p><a href="https://camo.githubusercontent.com/0ea4df88d6c05538a2d09a4b23a69c0fde2919fd8c113a6458f40d627e25ad3c/68747470733a2f2f70312d6a75656a696e2e62797465696d672e636f6d2f746f732d636e2d692d6b3375316662706663702f61656337323036363236316134646430383534613162633934633935646133367e74706c762d6b3375316662706663702d77617465726d61726b2e696d616765"><img src="https://camo.githubusercontent.com/0ea4df88d6c05538a2d09a4b23a69c0fde2919fd8c113a6458f40d627e25ad3c/68747470733a2f2f70312d6a75656a696e2e62797465696d672e636f6d2f746f732d636e2d692d6b3375316662706663702f61656337323036363236316134646430383534613162633934633935646133367e74706c762d6b3375316662706663702d77617465726d61726b2e696d616765" alt="错误日志文件信息"></a></p>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;\assets\css\APlayer.min.css&quot;&gt;&lt;script src=&quot;\assets\js\APlayer.min.js&quot; cla</summary>
      
    
    
    
    <category term="Python" scheme="https://nerkeler.github.io/categories/Python/"/>
    
    
    <category term="转载" scheme="https://nerkeler.github.io/tags/%E8%BD%AC%E8%BD%BD/"/>
    
    <category term="python" scheme="https://nerkeler.github.io/tags/python/"/>
    
    <category term="日志" scheme="https://nerkeler.github.io/tags/%E6%97%A5%E5%BF%97/"/>
    
  </entry>
  
  <entry>
    <title>动漫之家《镖人》爬虫</title>
    <link href="https://nerkeler.github.io/2023/03/31/%E5%8A%A8%E6%BC%AB%E4%B9%8B%E5%AE%B6%E3%80%8A%E9%95%96%E4%BA%BA%E3%80%8B%E7%88%AC%E8%99%AB/"/>
    <id>https://nerkeler.github.io/2023/03/31/%E5%8A%A8%E6%BC%AB%E4%B9%8B%E5%AE%B6%E3%80%8A%E9%95%96%E4%BA%BA%E3%80%8B%E7%88%AC%E8%99%AB/</id>
    <published>2023-03-31T02:21:00.000Z</published>
    <updated>2025-05-25T07:24:08.070Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h4 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h4><p>初学python时写的爬虫小程序，爬取动漫之家  《镖人》漫画所有文件到本地，单线程爬取，速度一般，如果用多线程会有很大速度提升，抽取爬虫逻辑可爬全站漫画资源</p><h4 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h4><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> os</span><br><span class="line"><span class="keyword">import</span> re</span><br><span class="line"><span class="keyword">import</span> requests</span><br><span class="line"><span class="keyword">from</span> bs4 <span class="keyword">import</span> BeautifulSoup</span><br><span class="line"><span class="keyword">from</span> tqdm <span class="keyword">import</span> tqdm</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># 获取当前漫画总链接文本</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">get_url</span>(<span class="params">url</span>):</span><br><span class="line">    headers = &#123;</span><br><span class="line">        <span class="string">&quot;User-Agent&quot;</span>: <span class="string">&quot;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 Safari/537.36&quot;</span>&#125;</span><br><span class="line">    res = requests.get(url, headers=headers)</span><br><span class="line">    res.encoding = res.apparent_encoding</span><br><span class="line">    <span class="keyword">if</span> res.status_code == <span class="number">200</span>:</span><br><span class="line">        <span class="keyword">return</span> res.text</span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">None</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># 爬取所有章节标题、链接</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">get_page</span>(<span class="params">html</span>):</span><br><span class="line">    title_list, href_list = [], []</span><br><span class="line"></span><br><span class="line">    soups = BeautifulSoup(html, <span class="string">&quot;lxml&quot;</span>)</span><br><span class="line">    soups = soups.find(attrs=&#123;<span class="string">&quot;class&quot;</span>: <span class="string">&quot;list_con_li autoHeight&quot;</span>&#125;)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> soup <span class="keyword">in</span> soups.select(<span class="string">&quot;li a&quot;</span>):</span><br><span class="line">        title_list.insert(<span class="number">0</span>, soup[<span class="string">&#x27;title&#x27;</span>])</span><br><span class="line">        href_list.insert(<span class="number">0</span>, soup[<span class="string">&#x27;href&#x27;</span>])</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> title_list, href_list  <span class="comment"># 返回列表</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># 判断是否为数字型字符串</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">is_number</span>(<span class="params">s</span>):</span><br><span class="line">    <span class="keyword">try</span>:</span><br><span class="line">        <span class="built_in">float</span>(s)</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">True</span></span><br><span class="line">    <span class="keyword">except</span> ValueError:</span><br><span class="line">        <span class="keyword">pass</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># 爬取章节内所有图片</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">main_download</span>(<span class="params">name, url</span>):</span><br><span class="line">    headers = &#123;</span><br><span class="line">        <span class="string">&quot;User-Agent&quot;</span>: <span class="string">&quot;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 Safari/537.36&quot;</span>,</span><br><span class="line">        <span class="string">&quot;Referer&quot;</span>: url&#125;  <span class="comment"># 浏览器标识</span></span><br><span class="line">    res = requests.get(url, headers=headers)</span><br><span class="line">    html = res.text  <span class="comment"># 动态页面，返回js代码</span></span><br><span class="line">    link = re.findall(<span class="string">&quot;function\(p,a,c,k,e,d\).*?split&quot;</span>, html)  <span class="comment"># 匹配图片代码片段</span></span><br><span class="line">    <span class="string">&quot;&quot;&quot;解码js，构造图片真实链接&quot;&quot;&quot;</span></span><br><span class="line">    first_number = link[<span class="number">0</span>].split(<span class="string">&quot;&#x27;.split&quot;</span>)</span><br><span class="line">    first_number = first_number[<span class="number">0</span>].split(<span class="string">&quot;||&quot;</span>)</span><br><span class="line">    links, second = [], []</span><br><span class="line">    first = <span class="string">&quot;&quot;</span></span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> first_number:</span><br><span class="line">        number = i.split(<span class="string">&quot;|&quot;</span>)</span><br><span class="line">        <span class="keyword">for</span> num <span class="keyword">in</span> number:</span><br><span class="line">            <span class="keyword">try</span>:</span><br><span class="line">                <span class="keyword">if</span> is_number(num) <span class="keyword">and</span> <span class="built_in">len</span>(num) == <span class="number">4</span>:</span><br><span class="line">                    first = num  <span class="comment"># 链接开始四位数数字串</span></span><br><span class="line">                <span class="keyword">elif</span> is_number(num) <span class="keyword">and</span> (<span class="built_in">len</span>(num) == <span class="number">5</span> <span class="keyword">or</span> <span class="built_in">len</span>(num) == <span class="number">6</span>):</span><br><span class="line">                    second.append(<span class="built_in">int</span>(num))  <span class="comment"># 链接中间数字串</span></span><br><span class="line">                <span class="keyword">elif</span> is_number(num) <span class="keyword">and</span> <span class="built_in">len</span>(num) &gt;= <span class="number">7</span>:</span><br><span class="line">                    links.append(num)  <span class="comment"># 链接末尾数字串</span></span><br><span class="line"></span><br><span class="line">            <span class="keyword">except</span>:</span><br><span class="line">                <span class="keyword">pass</span></span><br><span class="line">    links = <span class="built_in">sorted</span>(links)  <span class="comment"># 由小到大排序</span></span><br><span class="line">    <span class="comment"># 开始下载图片</span></span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> links:</span><br><span class="line">        imgs_link = <span class="string">f&#x27;https://images.dmzj.com/img/chapterpic/<span class="subst">&#123;first&#125;</span>/<span class="subst">&#123;second[<span class="number">0</span>]&#125;</span>/<span class="subst">&#123;i&#125;</span>.jpg&#x27;</span>  <span class="comment"># 构造真实链接</span></span><br><span class="line"></span><br><span class="line">        response = requests.get(url=imgs_link, headers=headers)</span><br><span class="line">        <span class="keyword">try</span>:</span><br><span class="line">            <span class="keyword">with</span> <span class="built_in">open</span>(<span class="string">f&quot;镖人/<span class="subst">&#123;name&#125;</span>/<span class="subst">&#123;i&#125;</span>.jpg&quot;</span>, <span class="string">&#x27;wb&#x27;</span>) <span class="keyword">as</span> f:</span><br><span class="line">                f.write(response.content)</span><br><span class="line">        <span class="keyword">except</span>:</span><br><span class="line">            <span class="keyword">pass</span></span><br><span class="line">    <span class="built_in">print</span>(<span class="string">f&quot;<span class="subst">&#123;name&#125;</span>: 已经下载完成&quot;</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># 主循环</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">main</span>():</span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">not</span> os.path.exists(<span class="string">&quot;镖人&quot;</span>):  <span class="comment"># 创建总文件夹</span></span><br><span class="line">        os.mkdir(<span class="string">&quot;镖人&quot;</span>)</span><br><span class="line">    url = <span class="string">&quot;https://www.dmzj.com/info/biaoren.html&quot;</span></span><br><span class="line">    html = get_url(url)</span><br><span class="line">    title_list, href_list = get_page(html)</span><br><span class="line">    <span class="comment">#processing = tqdm(range(0,len(title_list)))</span></span><br><span class="line">    <span class="keyword">for</span> name, url <span class="keyword">in</span> <span class="built_in">zip</span>(title_list, href_list,):</span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">not</span> os.path.exists(<span class="string">f&quot;镖人/<span class="subst">&#123;name&#125;</span>&quot;</span>):</span><br><span class="line">            os.mkdir(<span class="string">f&quot;镖人/<span class="subst">&#123;name&#125;</span>&quot;</span>)</span><br><span class="line">        main_download(name, url)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&#x27;__main__&#x27;</span>:</span><br><span class="line">    main()</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;\assets\css\APlayer.min.css&quot;&gt;&lt;script src=&quot;\assets\js\APlayer.min.js&quot; cla</summary>
      
    
    
    
    <category term="Python" scheme="https://nerkeler.github.io/categories/Python/"/>
    
    
    <category term="爬虫" scheme="https://nerkeler.github.io/tags/%E7%88%AC%E8%99%AB/"/>
    
  </entry>
  
  <entry>
    <title>Java日期小工具类</title>
    <link href="https://nerkeler.github.io/2023/03/30/Java%E6%97%A5%E6%9C%9F%E5%B0%8F%E5%B7%A5%E5%85%B7%E7%B1%BB/"/>
    <id>https://nerkeler.github.io/2023/03/30/Java%E6%97%A5%E6%9C%9F%E5%B0%8F%E5%B7%A5%E5%85%B7%E7%B1%BB/</id>
    <published>2023-03-30T08:04:00.000Z</published>
    <updated>2025-05-25T07:24:25.602Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h2 id="Java-日期月份处理小工具类"><a href="#Java-日期月份处理小工具类" class="headerlink" title="Java 日期月份处理小工具类"></a>Java 日期月份处理小工具类</h2><p>再<code>sql</code>的月份和日期查询中，可能会需要将日期或者月份相应推迟&#x2F;提前一天或一段时间，当然，这个逻辑放在<code>sql</code>中也是可以的，具体还是要看需求是否合适。分享三个小方法，一个是计算月份差、日期加减一&#x2F;多天、月份加减一&#x2F;多天</p><h4 id="月份差"><a href="#月份差" class="headerlink" title="月份差"></a>月份差</h4><h6 id="输入参数："><a href="#输入参数：" class="headerlink" title="输入参数："></a>输入参数：</h6><ul><li><code>startMonth</code><ul><li>字符串，格式应该为<code>yyyy-MM</code>对应下面<code>SimpleDateFormat(&quot;yyyy-MM&quot;);</code>可根据需要更改格式</li></ul></li><li><code>endMonth</code><ul><li>字符串，格式应该为<code>yyyy-MM</code>对应下面<code>SimpleDateFormat(&quot;yyyy-MM&quot;);</code>可根据需要更改格式</li></ul></li></ul><h6 id="返回值"><a href="#返回值" class="headerlink" title="返回值"></a>返回值</h6><ul><li>diff<ul><li>整型，月份差值，如3月-6月  返回3</li></ul></li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span>  <span class="type">int</span> <span class="title function_">getMonthDiff</span><span class="params">(String startMonth, String endMonth)</span> &#123;</span><br><span class="line"><span class="type">DateFormat</span> <span class="variable">dateFormat</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">SimpleDateFormat</span>(<span class="string">&quot;yyyy-MM&quot;</span>);</span><br><span class="line">    <span class="type">Date</span> <span class="variable">start</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">    <span class="type">Date</span> <span class="variable">end</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        start = dateFormat.parse(startMonth);</span><br><span class="line">        end = dateFormat.parse(endMonth);</span><br><span class="line">    &#125; <span class="keyword">catch</span> (ParseException e) &#123;</span><br><span class="line">        e.printStackTrace();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="type">Calendar</span> <span class="variable">c1</span> <span class="operator">=</span> Calendar.getInstance();</span><br><span class="line">    <span class="type">Calendar</span> <span class="variable">c2</span> <span class="operator">=</span> Calendar.getInstance();</span><br><span class="line">    c1.setTime(start);</span><br><span class="line">    c2.setTime(end);</span><br><span class="line">    <span class="type">int</span> <span class="variable">year1</span> <span class="operator">=</span> c1.get(Calendar.YEAR);</span><br><span class="line">    <span class="type">int</span> <span class="variable">year2</span> <span class="operator">=</span> c2.get(Calendar.YEAR);</span><br><span class="line">    <span class="type">int</span> <span class="variable">month1</span> <span class="operator">=</span> c1.get(Calendar.MONTH);</span><br><span class="line">    <span class="type">int</span> <span class="variable">month2</span> <span class="operator">=</span> c2.get(Calendar.MONTH);</span><br><span class="line">    <span class="type">int</span> <span class="variable">day1</span> <span class="operator">=</span> c1.get(Calendar.DAY_OF_MONTH);</span><br><span class="line">    <span class="type">int</span> <span class="variable">day2</span> <span class="operator">=</span> c2.get(Calendar.DAY_OF_MONTH);</span><br><span class="line">    <span class="comment">// 获取年的差值</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">yearInterval</span> <span class="operator">=</span> year1 - year2;</span><br><span class="line">    <span class="comment">// 如果 d1的 月-日 小于 d2的 月-日 那么 yearInterval-- 这样就得到了相差的年数</span></span><br><span class="line">    <span class="keyword">if</span> (month1 &lt; month2 || month1 == month2 &amp;&amp; day1 &lt; day2) &#123;</span><br><span class="line">        yearInterval--;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 获取月数差值</span></span><br><span class="line">    <span class="type">int</span> <span class="variable">monthInterval</span> <span class="operator">=</span> (month1 + <span class="number">12</span>) - month2;</span><br><span class="line">    <span class="keyword">if</span> (day1 &lt; day2) &#123;</span><br><span class="line">        monthInterval--;</span><br><span class="line">    &#125;</span><br><span class="line">    monthInterval %= <span class="number">12</span>;</span><br><span class="line">    <span class="type">int</span> <span class="variable">monthsDiff</span> <span class="operator">=</span> Math.abs(yearInterval * <span class="number">12</span> + monthInterval);</span><br><span class="line">    <span class="keyword">return</span> monthsDiff;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="日期加减"><a href="#日期加减" class="headerlink" title="日期加减"></a>日期加减</h4><h6 id="输入参数：-1"><a href="#输入参数：-1" class="headerlink" title="输入参数："></a>输入参数：</h6><ul><li><code>day</code><ul><li>字符串，日期，格式应该为<code>yyyy-MM-dd</code>对应下面<code>SimpleDateFormat(&quot;yyyy-MM-dd&quot;);</code>可根据需要更改格式</li></ul></li><li><code>num</code><ul><li>整型，偏移量   为正 时间往后加，负  时间往前推</li></ul></li></ul><h6 id="返回值-1"><a href="#返回值-1" class="headerlink" title="返回值"></a>返回值</h6><ul><li>date<ul><li>String，格式处理后的时间，如3月1-3月4  返回3</li></ul></li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> String <span class="title function_">dayFormat</span><span class="params">(String day, <span class="type">int</span> num)</span> &#123;</span><br><span class="line">       <span class="type">DateFormat</span> <span class="variable">dateFormat</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">SimpleDateFormat</span>(<span class="string">&quot;yyyy-MM-dd&quot;</span>);</span><br><span class="line">       <span class="type">Date</span> <span class="variable">date</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">       <span class="keyword">try</span> &#123;</span><br><span class="line">           date = dateFormat.parse(day);</span><br><span class="line">       &#125; <span class="keyword">catch</span> (ParseException e) &#123;</span><br><span class="line">           e.printStackTrace();</span><br><span class="line">       &#125;</span><br><span class="line">       <span class="type">Calendar</span> <span class="variable">c</span> <span class="operator">=</span> Calendar.getInstance();</span><br><span class="line">       c.setTime(date);</span><br><span class="line">       c.add(Calendar.DATE,num);</span><br><span class="line">       date = c.getTime();</span><br><span class="line">       <span class="keyword">return</span>  dateFormat.format(date);</span><br><span class="line">   &#125;</span><br></pre></td></tr></table></figure><h4 id="月份加减"><a href="#月份加减" class="headerlink" title="月份加减"></a>月份加减</h4><h6 id="输入参数：-2"><a href="#输入参数：-2" class="headerlink" title="输入参数："></a>输入参数：</h6><ul><li><code>day</code><ul><li>字符串，日期，格式应该为<code>yyyy-MM</code>对应下面<code>SimpleDateFormat(&quot;yyyy-MM&quot;);</code>可根据需要更改格式</li></ul></li><li><code>num</code><ul><li>整型，偏移量   为正 月份往后加，负  时间往前推</li></ul></li></ul><h6 id="返回值-2"><a href="#返回值-2" class="headerlink" title="返回值"></a>返回值</h6><ul><li>date<ul><li>String，格式处理后的时间，如3月-6月  返回3</li></ul></li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> String <span class="title function_">monthFormat</span><span class="params">(String month, <span class="type">int</span> num)</span> &#123;</span><br><span class="line">       <span class="type">DateFormat</span> <span class="variable">dateFormat</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">SimpleDateFormat</span>(<span class="string">&quot;yyyy-MM&quot;</span>);</span><br><span class="line">       <span class="type">Date</span> <span class="variable">date</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">       <span class="keyword">try</span> &#123;</span><br><span class="line">           date = dateFormat.parse(month);</span><br><span class="line">       &#125; <span class="keyword">catch</span> (ParseException e) &#123;</span><br><span class="line">           e.printStackTrace();</span><br><span class="line">       &#125;</span><br><span class="line">       <span class="type">Calendar</span> <span class="variable">c</span> <span class="operator">=</span> Calendar.getInstance();</span><br><span class="line">       c.setTime(date);</span><br><span class="line">       c.add(Calendar.MONTH,num);</span><br><span class="line">       date = c.getTime();</span><br><span class="line">       <span class="keyword">return</span>  dateFormat.format(date);</span><br><span class="line">   &#125;</span><br></pre></td></tr></table></figure><h4 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h4><p>最近太水了，已经托更一周了，这次又是水了一篇，争取下次好好写一篇</p>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;\assets\css\APlayer.min.css&quot;&gt;&lt;script src=&quot;\assets\js\APlayer.min.js&quot; cla</summary>
      
    
    
    
    <category term="Java" scheme="https://nerkeler.github.io/categories/Java/"/>
    
    
  </entry>
  
  <entry>
    <title>windows本地密码管理器</title>
    <link href="https://nerkeler.github.io/2023/03/20/windows%E5%AF%86%E7%A0%81%E7%AE%A1%E7%90%86%E5%99%A8/"/>
    <id>https://nerkeler.github.io/2023/03/20/windows%E5%AF%86%E7%A0%81%E7%AE%A1%E7%90%86%E5%99%A8/</id>
    <published>2023-03-20T00:42:00.000Z</published>
    <updated>2025-05-25T07:24:53.760Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h2 id="本地密码管理器4-0"><a href="#本地密码管理器4-0" class="headerlink" title="本地密码管理器4.0"></a>本地密码管理器4.0</h2><p><code>github</code>地址：<a href="https://github.com/nerkeler/account.git">https://github.com/nerkeler/account.git</a></p><p>蓝奏云：<a href="https://wwtf.lanzoul.com/b01ereqoh">https://wwtf.lanzoul.com/b01ereqoh</a> 密码:9920<br>文叔叔：<a href="https://t.wss.ink/f/b4jc2yer65e">https://t.wss.ink/f/b4jc2yer65e</a> </p><h4 id="重要提示"><a href="#重要提示" class="headerlink" title="重要提示"></a>重要提示</h4><p>程序默认密码：<strong>password</strong></p><p>密钥位置：<code>./resource/aesKey   </code>     建议另存一份以作备份</p><h4 id="修改点"><a href="#修改点" class="headerlink" title="修改点"></a>修改点</h4><ul><li>本地密码导出中文乱码修复</li><li>引入日志记录</li><li>优化edge&#x2F;chrome 浏览器导出适配管理</li></ul><p><em>PS: 程序第一次打开关于页面显示不完整，如果遇到，请关闭后再打开</em></p><p>&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;</p><h4 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h4><p>闲来无事，看到自己有很多网站的账户密码，有些网站可能打开一两次也就忘记了，下一次在输入账户密码就想不起来，这样很容易丢失账号（当然也可以保存在浏览器自带的密码管理器中），虽然市面上也有很多优秀的账户密码管理软件，一来是这些程序大都是联网运行，在提供了多端存档的同时，也将密码和账户在网络上传输，虽然实际上很安全，但是并不是绝对的安全，二来，部分优秀的程序都是订阅付费机制，就想着自己干脆写个简单的本地的账户管理器，于是就有了这个小程序</p><p>先说一下优点吧，同市面上的程序比较，一个最突出的优点就是完全运行在本地，账户密码经过<code>AES</code>加密，在逻辑上可以说是非常安全的，因为你的密码不会暴漏在网络上，本地也进行了加密处理。</p><p>当然缺点也很明显，一是功能不是十分完善，二是该程序使用<code>tkinter</code> 模块编写，在布局和界面展示上都显得十分简陋，好在基本的功能没有问题</p><h4 id="使用说明"><a href="#使用说明" class="headerlink" title="使用说明"></a>使用说明</h4><ol><li>下载解压程序文件</li><li>运行<code>account.exe</code>文件</li><li>点击<code>新建账号</code>按钮添加新的账号密码信息</li><li>双击表格中的数据进行编辑或删除</li><li>点击<code>导出</code>按钮验证密钥密码后将数据导出为CSV格式文件</li><li>点击<code>导入</code>按钮选择CSV文件进行导入数据</li><li>可按需求导入并管理<code>Chrome/Edge密码本</code>浏览器密码</li></ol><h4 id="登录"><a href="#登录" class="headerlink" title="登录"></a>登录</h4><p><img src="https://pic.imgdb.cn/item/6415d3c2a682492fcc12ae16.png"></p><p>第一次登陆密钥弹出框被关闭时，就进入了程序的登录界面，默认账户名admin 不可修改，默认密码为 ：**<code>password</code>**，建议第一次登陆时修改密码</p><p><em><strong>PS: 多用户功能尚未开发，因此账户名锁定不可编辑</strong></em></p><h4 id="修改密码"><a href="#修改密码" class="headerlink" title="修改密码"></a>修改密码</h4><h4 id=""><a href="#" class="headerlink" title=""></a></h4><p><img src="https://pic.imgdb.cn/item/642fd3b4a682492fcc59f817.png"></p><p>在登陆界面 点击   <strong>改密</strong>  进入修改密码界面，新密码要求字符数大于8位，点击<strong>确定</strong> 按钮即可进行密码修改，修改成功后会提示修改成功，重新登陆，如下图所示</p><p><img src="https://pic.imgdb.cn/item/6415d527a682492fcc1573f4.png"></p><h4 id="chrome-x2F-edge浏览器密码导入管理"><a href="#chrome-x2F-edge浏览器密码导入管理" class="headerlink" title="chrome&#x2F;edge浏览器密码导入管理"></a>chrome&#x2F;edge浏览器密码导入管理</h4><p>依次点击：</p><ol><li><p>设置</p></li><li><p>自动填充</p></li><li><p>密码管理器</p><p><strong>找到如图红框内容，验证设备密码后导出chrome密码本csv文件到本地</strong></p><p><img src="https://pic2.imgdb.cn/item/645366670d2dde5777904d8e.png"></p></li></ol><p>回到程序后点击导入</p><p>对应导入源后选择导入文件即可导入数据，<strong>单线程导入较慢，请耐心等待</strong>，快捷键暂未适配</p><p><img src="https://pic2.imgdb.cn/item/645366ec0d2dde5777911e80.png"></p><h4 id="主页面"><a href="#主页面" class="headerlink" title="主页面"></a>主页面</h4><p>主界面略作修改</p><p><img src="https://pic2.imgdb.cn/item/645365530d2dde57778ec79f.png"></p><p>主界面共分为两个区域：</p><ul><li>一个是上方的功能区，提供了查询、查看、新增、删除（软删除）功能，</li><li>一个是下方的账户展示区域，默认打开时按照列表展示所有账户信息</li></ul><h5 id="相关操作"><a href="#相关操作" class="headerlink" title="相关操作"></a>相关操作</h5><ul><li><p>下拉框提供了  账户编号（对应下方序号列）、网站名称（对应下方网站列）、网站网址（对应下方网址列）查询条件转换</p></li><li><p>文本框接受  下拉框的对应字段，<strong>为空时默认查询所有</strong></p></li><li><p>点击某一项账户时，<strong>点击查看&#x2F;双击当前项</strong>  可进入当前账户查看界面，如图所示，当前页面也可以更新账户信息</p></li><li><p>新增按钮会弹出新增界面，按照规定指示填写字段即可新增一条记录</p></li><li><p>删除按钮  在点击某一项账户时，点击删除，即可删除当前记录（软删除，更改了当前的状态）</p></li></ul><h4 id="随机密码"><a href="#随机密码" class="headerlink" title="随机密码"></a>随机密码</h4><p>可以按照自己的需求生成相应的密码规则，<code>ABC</code>&#x2F;<code>123</code>&#x2F;<code>abc</code>&#x2F;<code>#$&amp;</code></p><p><em>注：在密码长度&gt;字符类型数的时候会补数字  如  密码长度选30  规则选<code>abc</code>  会在26个字符后再补几个数字构成随机字符</em></p><p><img src="https://pic.imgdb.cn/item/6424f580a682492fcc9f685c.png"></p><h4 id="关于"><a href="#关于" class="headerlink" title="关于"></a>关于</h4><p>聊胜于无，简单说一下，详细见程序页面</p><h4 id="补充说明"><a href="#补充说明" class="headerlink" title="补充说明"></a>补充说明</h4><p>忘记登陆账户密码时，可将<code>account.db</code> 文件使用数据库可视化工具打开，取出当前账户的相关信息和密码，根据密钥和加密逻辑解密当前账户的密码明文</p><p>加密逻辑如下：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> hashlib</span><br><span class="line"><span class="keyword">from</span> Crypto.Cipher <span class="keyword">import</span> AES</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">encode_password</span>(<span class="params">password</span>):</span><br><span class="line">key = yourkey<span class="comment"># key 表示你的密钥 </span></span><br><span class="line">    </span><br><span class="line">slot = <span class="string">&quot;nerkeler&quot;</span></span><br><span class="line">    encode = key + slot</span><br><span class="line">    myKey = hashlib.md5(encode.encode(<span class="string">&quot;utf-8&quot;</span>)).hexdigest().__str__()[:<span class="number">24</span>]</span><br><span class="line">    <span class="keyword">return</span> aes_encode(myKey, password)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">aes_decode</span>(<span class="params">key, ciphertext</span>):</span><br><span class="line">    <span class="comment"># 将密钥填充到16的倍数</span></span><br><span class="line">    key = key + (<span class="number">16</span> - <span class="built_in">len</span>(key) % <span class="number">16</span>) * <span class="string">&#x27;\0&#x27;</span></span><br><span class="line">    <span class="comment"># 创建AES密码器对象</span></span><br><span class="line">    cipher = AES.new(key.encode(), AES.MODE_ECB)</span><br><span class="line">    <span class="comment"># 解码Base64编码的密文</span></span><br><span class="line">    ciphertext = base64.b64decode(ciphertext)</span><br><span class="line">    <span class="comment"># 解密</span></span><br><span class="line">    plaintext = cipher.decrypt(ciphertext)</span><br><span class="line">    <span class="comment"># 去除填充字符</span></span><br><span class="line">    plaintext = plaintext.rstrip(<span class="string">b&#x27;\0&#x27;</span>)</span><br><span class="line">    <span class="keyword">return</span> plaintext.decode()</span><br></pre></td></tr></table></figure><h4 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h4><p>后期打算使用<code>pyside6</code>重写一版，<code>Tkinter</code>版本不准备大改动了，如果有使用建议和bug,欢迎打扰，详见关于页</p>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;\assets\css\APlayer.min.css&quot;&gt;&lt;script src=&quot;\assets\js\APlayer.min.js&quot; cla</summary>
      
    
    
    
    <category term="Python" scheme="https://nerkeler.github.io/categories/Python/"/>
    
    
    <category term="python" scheme="https://nerkeler.github.io/tags/python/"/>
    
    <category term="tkinter" scheme="https://nerkeler.github.io/tags/tkinter/"/>
    
  </entry>
  
  <entry>
    <title>对称加密和非对称加密</title>
    <link href="https://nerkeler.github.io/2023/03/10/%E5%AF%B9%E7%A7%B0%E5%8A%A0%E5%AF%86%E5%92%8C%E9%9D%9E%E5%AF%B9%E7%A7%B0%E5%8A%A0%E5%AF%86/"/>
    <id>https://nerkeler.github.io/2023/03/10/%E5%AF%B9%E7%A7%B0%E5%8A%A0%E5%AF%86%E5%92%8C%E9%9D%9E%E5%AF%B9%E7%A7%B0%E5%8A%A0%E5%AF%86/</id>
    <published>2023-03-10T01:52:00.000Z</published>
    <updated>2023-03-30T08:10:02.000Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="\assets\css\APlayer.min.css"><script src="\assets\js\APlayer.min.js" class="aplayer-secondary-script-marker"></script><h2 id="对称加密AES和非对称加密RSA"><a href="#对称加密AES和非对称加密RSA" class="headerlink" title="对称加密AES和非对称加密RSA"></a>对称加密AES和非对称加密RSA</h2><h3 id="AES（-Advanced-Encryption-Standard-）"><a href="#AES（-Advanced-Encryption-Standard-）" class="headerlink" title="AES（ Advanced Encryption Standard ）"></a>AES（ Advanced Encryption Standard ）</h3><p><em>对称加密算法</em>，<em>加密和解密过程使用同一个密钥</em></p><p>AES为分组加密法，把明文分成一组一组的，每组长度相等，每次加密一组数据，直到加密完整个明文，在AES标准规范中，分组长度只能是128位，AES是按照字节进行加密的，也就是说每个分组为16个字节（每个字节8位）。密钥的长度可以使用128位、192位或256位。这导致密钥长度不同，推荐加密的轮数也不同。</p><p><strong>因为明文和密钥必须是128位（密钥可以是192&#x2F;256 位），所以需要将分组明文和密钥进行补位</strong></p><h4 id="python代码实现"><a href="#python代码实现" class="headerlink" title="python代码实现"></a>python代码实现</h4><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> Crypto.Cipher <span class="keyword">import</span> AES</span><br><span class="line"><span class="keyword">import</span> base64</span><br><span class="line"></span><br><span class="line"><span class="comment"># 加密  ECB模式  16代表16字节也就是128位</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">aes_encode</span>(<span class="params">key, password</span>):</span><br><span class="line">    <span class="comment"># 将密钥填充到16的倍数</span></span><br><span class="line">    key = key + (<span class="number">16</span> - <span class="built_in">len</span>(key) % <span class="number">16</span>) * <span class="string">&#x27;\0&#x27;</span></span><br><span class="line">    <span class="comment"># 创建AES密码器对象</span></span><br><span class="line">    cipher = AES.new(key.encode(), AES.MODE_ECB)</span><br><span class="line">    <span class="comment"># 将明文填充到16的倍数</span></span><br><span class="line">    password = password + (<span class="number">16</span> - <span class="built_in">len</span>(password) % <span class="number">16</span>) * <span class="string">&#x27;\0&#x27;</span></span><br><span class="line">    <span class="comment"># 加密</span></span><br><span class="line">    ciphertext = cipher.encrypt(password.encode())</span><br><span class="line">    <span class="comment"># 将密文进行Base64编码</span></span><br><span class="line">    ciphertext = base64.b64encode(ciphertext)</span><br><span class="line">    <span class="keyword">return</span> ciphertext.decode()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># 解密</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">aes_decode</span>(<span class="params">key, ciphertext</span>):</span><br><span class="line">    <span class="comment"># 将密钥填充到16的倍数</span></span><br><span class="line">    key = key + (<span class="number">16</span> - <span class="built_in">len</span>(key) % <span class="number">16</span>) * <span class="string">&#x27;\0&#x27;</span></span><br><span class="line">    <span class="comment"># 创建AES密码器对象</span></span><br><span class="line">    cipher = AES.new(key.encode(), AES.MODE_ECB)</span><br><span class="line">    <span class="comment"># 解码Base64编码的密文</span></span><br><span class="line">    ciphertext = base64.b64decode(ciphertext)</span><br><span class="line">    <span class="comment"># 解密</span></span><br><span class="line">    plaintext = cipher.decrypt(ciphertext)</span><br><span class="line">    <span class="comment"># 去除填充字符</span></span><br><span class="line">    plaintext = plaintext.rstrip(<span class="string">b&#x27;\0&#x27;</span>)</span><br><span class="line">    <span class="keyword">return</span> plaintext.decode()</span><br><span class="line"></span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="RSA"><a href="#RSA" class="headerlink" title="RSA"></a>RSA</h2><p>RSA是一种公钥加密算法，它可以用于保护数据的机密性和完整性。它基于两个大质数的乘积难以被分解的数学问题，通过这种问题的解决来保护数据的安全性。</p><p>RSA算法包括三个步骤：密钥生成、加密和解密。在密钥生成过程中，首先选择两个大质数p和q，计算它们的乘积n&#x3D;p<em>q，并选择一个整数e，使得e和(p-1)</em>(q-1)互质。然后计算d，使得(d<em>e) mod((p-1)</em>(q-1))&#x3D;1。公钥是(e,n)，私钥是(d,n)。</p><p>在加密过程中，要发送的消息m被转换为整数M，并用公钥(e,n)进行加密，计算C&#x3D;M^e mod n。</p><p>在解密过程中，使用私钥(d,n)对加密的消息进行解密，计算M&#x3D;C^d mod n。</p><figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> Crypto.PublicKey <span class="keyword">import</span> RSA</span><br><span class="line"><span class="keyword">from</span> Crypto.Cipher <span class="keyword">import</span> PKCS1_v1_5</span><br><span class="line"><span class="keyword">import</span> base64</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">generate_key_pair</span>():</span><br><span class="line">    <span class="string">&quot;&quot;&quot;生成RSA密钥对&quot;&quot;&quot;</span></span><br><span class="line">    key_pair = RSA.generate(<span class="number">2048</span>)</span><br><span class="line">    private_key = key_pair.export_key()</span><br><span class="line">    public_key = key_pair.publickey().export_key()</span><br><span class="line">    <span class="keyword">return</span> private_key, public_key</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">encrypt</span>(<span class="params">public_key, plaintext</span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;RSA加密&quot;&quot;&quot;</span></span><br><span class="line">    key = RSA.import_key(public_key)</span><br><span class="line">    cipher = PKCS1_v1_5.new(key)</span><br><span class="line">    ciphertext = cipher.encrypt(plaintext.encode(<span class="string">&#x27;utf-8&#x27;</span>))</span><br><span class="line">    <span class="keyword">return</span> base64.b64encode(ciphertext).decode(<span class="string">&#x27;utf-8&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">decrypt</span>(<span class="params">private_key, ciphertext</span>):</span><br><span class="line">    <span class="string">&quot;&quot;&quot;RSA解密&quot;&quot;&quot;</span></span><br><span class="line">    key = RSA.import_key(private_key)</span><br><span class="line">    cipher = PKCS1_v1_5.new(key)</span><br><span class="line">    ciphertext = base64.b64decode(ciphertext.encode(<span class="string">&#x27;utf-8&#x27;</span>))</span><br><span class="line">    plaintext = cipher.decrypt(ciphertext, <span class="literal">None</span>).decode(<span class="string">&#x27;utf-8&#x27;</span>)</span><br><span class="line">    <span class="keyword">return</span> plaintext</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&#x27;__main__&#x27;</span>:</span><br><span class="line">    <span class="comment"># 生成RSA密钥对</span></span><br><span class="line">    private_key, public_key = generate_key_pair()</span><br><span class="line">    </span><br><span class="line">    <span class="comment"># unicode 转str</span></span><br><span class="line">    <span class="built_in">print</span>(private_key.decode(<span class="string">&quot;utf-8&quot;</span>))</span><br><span class="line">    <span class="built_in">print</span>(public_key.decode(<span class="string">&quot;utf-8&quot;</span>))</span><br><span class="line">    <span class="comment"># 加密消息</span></span><br><span class="line">    message = <span class="string">&#x27;Hello, world!&#x27;</span></span><br><span class="line">    ciphertext = encrypt(public_key, message)</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&#x27;加密后的消息：&#x27;</span>, ciphertext)</span><br><span class="line">    <span class="comment"># 解密消息</span></span><br><span class="line">    plaintext = decrypt(private_key, ciphertext)</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&#x27;解密后的消息：&#x27;</span>, plaintext)</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="摘要算法，非对称加密算法与对称加密算法-安全性-比较"><a href="#摘要算法，非对称加密算法与对称加密算法-安全性-比较" class="headerlink" title="摘要算法，非对称加密算法与对称加密算法 安全性 比较"></a>摘要算法，非对称加密算法与对称加密算法 安全性 比较</h3><p><em>引用博客  <a href="https://blog.betamao.me/posts/2021/24/pkcs-v1_5_misuse/">https://blog.betamao.me/posts/2021/24/pkcs-v1_5_misuse/</a></em></p><ul><li><strong>摘要算法</strong>通过单向陷门函数将任意长度映射到固定长度，这个过程是不可逆的，再加上它的抗碰撞性让它成为标识数据的好方法，这方面如果用的算法是老古董就可能受碰撞和<a href="http://blog.chinaunix.net/uid-27070210-id-3255947.html">长度扩展攻击</a>的影响。</li><li><strong>对称加密算法</strong>使用的加解密钥相同，可再分为流密码与分组密码，流密码将它的密钥作为随机数种子通过伪随机生成函数源源不断地生成随机数，明文与之异或就得到了密文，由于异或运算的特性密文与之异或也就恢复了明文，因此它的加解密算法是一样的，而且它针对的任意长度的数据。分组密码的密钥和加密长度是固定的，它会对加密密钥进行一些变换，再与明文进行运算，该过程可能进行多次因此加解密算法不一定相同，由于它只能加密固定长度的数据，而一般要加密的数据长度都不满足要求，此时就需要把数据分组，若分组后最后一组的长度不满足要求又需要对其填充，前者就是分组密码的工作模式。是否需要填充也和工作模式相关，如OFB模式就可以将过程转换为类似流密码的方式，而XTS使用密文窃取也不需要填充，另外即使长度刚好符合也可能需要填充，如PKCS7此时就需要再填充一个完整的分组，之前提到若PKCS7与CBC模式组合时，就有可能出现<a href="http://blog-archive.betamao.me/2019/11/15/shiro-po721%E5%88%A9%E7%94%A8%E7%AE%80%E6%9E%90/#padding-oracle">PaddingOracle</a>漏洞，即若服务端会返回解密状态则可以通过推测加密任何数据。</li><li><strong>非对称加密算法</strong>加解密不再使用相同的密钥，而且它的结构和对称密码有很大的不同，对称密码更像是逻辑位运算而非对称是数学运算，它利用的全是数学上的难题，就是(给一个限制)后只有正向计算的算法，而还没有逆向计算的算法(大整数分解，椭圆曲线离散对数，格最短向量等)，先随机生成符合要求的私钥，私钥到公钥的生成过程是正向的有算法因此很容易，而反过来就没有算法可恢复，通过公开公钥就可实现加密与签名功能，由于这种加密也是在有限的范围上完成的因此单次运算的数字大小有限制，为此也需要对其分组填充，RSA就有PKCS1 V1.5与PKCS OAEP。</li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;\assets\css\APlayer.min.css&quot;&gt;&lt;script src=&quot;\assets\js\APlayer.min.js&quot; cla</summary>
      
    
    
    
    <category term="Python" scheme="https://nerkeler.github.io/categories/Python/"/>
    
    
    <category term="python" scheme="https://nerkeler.github.io/tags/python/"/>
    
    <category term="加密算法" scheme="https://nerkeler.github.io/tags/%E5%8A%A0%E5%AF%86%E7%AE%97%E6%B3%95/"/>
    
  </entry>
  
</feed>
