<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <author>
    <name>chaofan</name>
    <email>chaofanxy@outlook.com</email>
  </author>
  <generator uri="https://hexo.io/">Hexo</generator>
  <icon>https://www.gravatar.com/avatar/ce5e1a3185414e98ac8c479f59a8e589</icon>
  <id>https://chaofan.xyz/</id>
  <link href="https://chaofan.xyz/" rel="alternate"/>
  <link href="https://chaofan.xyz/atom.xml" rel="self"/>
  <rights>All rights reserved 2026, chaofan</rights>
  <subtitle>念念不忘，必有回响 | chaofan.xyz</subtitle>
  <title>超凡’s Blog</title>
  <updated>2026-08-09T09:24:32.640Z</updated>
  <entry>
    <author>
      <name>chaofan</name>
      <email>chaofanxy@outlook.com</email>
    </author>
    <category term="Java" scheme="https://chaofan.xyz/categories/Java/"/>
    <category term="Java" scheme="https://chaofan.xyz/tags/Java/"/>
    <content>
      <![CDATA[<p>在Java并发包中，对于线程同步，有着<code>CountDownLatch</code>和<code>CyclicBarrier</code>两个类，两者有什么区别，怎样使用呢</p><span id="more"></span><h2 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h2><h3 id="CountDownLatch"><a href="#CountDownLatch" class="headerlink" title="CountDownLatch"></a>CountDownLatch</h3><p>CountDownLatch可以使一个获多个线程等待其他线程各自执行完毕后再执行。</p><p>CountDownLatch 定义了一个计数器，和一个阻塞队列， 当计数器的值递减为0之前，阻塞队列里面的线程处于挂起状态，当计数器递减到0时会唤醒阻塞队列所有线程，这里的计数器是一个标志，可以表示一个任务一个线程，也可以表示一个倒计时器，CountDownLatch可以解决那些一个或者多个线程在执行之前必须依赖于某些必要的前提业务先执行的场景。</p><h3 id="CyclicBarrier"><a href="#CyclicBarrier" class="headerlink" title="CyclicBarrier"></a>CyclicBarrier</h3><p>CyclicBarrier可以使一定数量的线程反复地在“栅栏”位置处汇集。</p><p>当线程到达“栅栏”位置时将调用await方法，这个方法将阻塞直到所有线程都到达“栅栏”位置。如果所有线程都到达“栅栏”位置，那么“栅栏”将打开，此时所有的线程都将被释放，而“栅栏”将被重置以便下次使用。</p><h2 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h2><p>可以通过两个简单的demo说明两者用法上的区别，首先是CyclicBarrier的例子：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><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="type">CyclicBarrier</span> <span class="variable">cb</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">CyclicBarrier</span>(<span class="number">3</span>, () -&gt; &#123;</span><br><span class="line">  System.out.println(<span class="string">&quot;Barrier reached&quot;</span>);</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="type">ExecutorService</span> <span class="variable">pool</span> <span class="operator">=</span> Executors.newFixedThreadPool(<span class="number">3</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">3</span>; i++) &#123;</span><br><span class="line">  pool.submit(() -&gt; &#123;</span><br><span class="line">    System.out.println(Thread.currentThread().getName());</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">      cb.await();</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">&#125;</span><br><span class="line">pool.close();</span><br></pre></td></tr></table></figure><p>可以看到，CyclicBarrier的<code>await()</code>方法执行后，会阻塞住当前线程，底层使用了<code>ReentrantLock::lock()</code>方法加锁，在所有线程都调用了<code>await()</code>使得CyclicBarrier计数器到达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></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (index == <span class="number">0</span>) &#123;  <span class="comment">// tripped</span></span><br><span class="line">    <span class="type">Runnable</span> <span class="variable">command</span> <span class="operator">=</span> barrierCommand;</span><br><span class="line">    <span class="keyword">if</span> (command != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            command.run();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Throwable ex) &#123;</span><br><span class="line">            breakBarrier();</span><br><span class="line">            <span class="keyword">throw</span> ex;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    nextGeneration();</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>最后回到调用<code>await()</code>的地方继续往下执行。</p><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></pre></td><td class="code"><pre><span class="line"><span class="type">CountDownLatch</span> <span class="variable">cd</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">CountDownLatch</span>(<span class="number">3</span>);</span><br><span class="line"><span class="type">ExecutorService</span> <span class="variable">pool</span> <span class="operator">=</span> Executors.newFixedThreadPool(<span class="number">3</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">3</span>; i++) &#123;</span><br><span class="line">  pool.submit(() -&gt; &#123;</span><br><span class="line">    cd.countDown();</span><br><span class="line">    System.out.println(Thread.currentThread().getName());</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">  cd.await();</span><br><span class="line">  System.out.println(<span class="string">&quot;ok&quot;</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">pool.close();</span><br></pre></td></tr></table></figure><p>可以看到，CountDownLatch和CyclicBarrier使用上还是有一定的区别，在调用<code>countDown()</code>之后并不会使当前线程阻塞，只是给内部的计时器递减1。</p><p>首先看下<code>await()</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="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">    sync.acquireSharedInterruptibly(<span class="number">1</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里会阻塞住线程。</p><p>然后是<code>countDown()</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="keyword">public</span> <span class="keyword">void</span> <span class="title function_">countDown</span><span class="params">()</span> &#123;</span><br><span class="line">    sync.releaseShared(<span class="number">1</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里sync是CountDownLatch内部类Sync的对象，而Sync又是java中AbstractQueuedSynchronizer(AQS)的派生类，所以可以看出CountDownLatch通过AQS实现。接下来继续看Sync中<code>tryReleaseShared(int)</code>的实现</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">protected</span> <span class="type">boolean</span> <span class="title function_">tryReleaseShared</span><span class="params">(<span class="type">int</span> releases)</span> &#123;</span><br><span class="line">    <span class="comment">// Decrement count; signal when transition to zero</span></span><br><span class="line">    <span class="keyword">for</span> (;;) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">c</span> <span class="operator">=</span> getState();</span><br><span class="line">        <span class="keyword">if</span> (c == <span class="number">0</span>)</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">        <span class="type">int</span> <span class="variable">nextc</span> <span class="operator">=</span> c - <span class="number">1</span>;</span><br><span class="line">        <span class="keyword">if</span> (compareAndSetState(c, nextc))</span><br><span class="line">            <span class="keyword">return</span> nextc == <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可以看到，这里使用CAS操作更新标记位，这里的标记位就是上面的计数器，当标记位为0后就会将阻塞的线程放行。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ul><li><p>CountdownLatch适用于所有线程通过某一点后通知方法，而CyclicBarrier则适合让所有线程在同一点同时执行</p></li><li><p>CountdownLatch利用继承AQS的共享锁来进行线程的通知,利用CAS来进行，而CyclicBarrier则利用ReentrantLock的Condition来阻塞和通知线程</p></li></ul>]]>
    </content>
    <id>https://chaofan.xyz/posts/6f3f7f34/</id>
    <link href="https://chaofan.xyz/posts/6f3f7f34/"/>
    <published>2024-02-29T22:56:05.000Z</published>
    <summary>
      <![CDATA[<p>在Java并发包中，对于线程同步，有着<code>CountDownLatch</code>和<code>CyclicBarrier</code>两个类，两者有什么区别，怎样使用呢</p>]]>
    </summary>
    <title>CountDownLatch和CyclicBarrier的区别</title>
    <updated>2026-08-09T09:24:32.640Z</updated>
  </entry>
  <entry>
    <author>
      <name>chaofan</name>
      <email>chaofanxy@outlook.com</email>
    </author>
    <category term="云原生" scheme="https://chaofan.xyz/categories/%E4%BA%91%E5%8E%9F%E7%94%9F/"/>
    <category term="告警" scheme="https://chaofan.xyz/categories/%E4%BA%91%E5%8E%9F%E7%94%9F/%E5%91%8A%E8%AD%A6/"/>
    <category term="运维" scheme="https://chaofan.xyz/categories/%E4%BA%91%E5%8E%9F%E7%94%9F/%E5%91%8A%E8%AD%A6/%E8%BF%90%E7%BB%B4/"/>
    <category term="alertmanager" scheme="https://chaofan.xyz/tags/alertmanager/"/>
    <category term="prometheus" scheme="https://chaofan.xyz/tags/prometheus/"/>
    <content>
      <![CDATA[<p><a href="https://prometheus.io/docs/alerting/latest/alertmanager/">Alertmanager</a>可以接收<a href="https://prometheus.io/">Prometheus</a>等客户端发来的告警，之后通过分组、去重等处理，将它们通过路由发送给正确的接收器。</p><span id="more"></span><p>如果你还不熟悉alertmanager，请先参阅<a href="https://prometheus.io/docs/alerting/latest/alertmanager/">alertmanager</a></p><h2 id="参数解释"><a href="#参数解释" class="headerlink" title="参数解释"></a>参数解释</h2><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># How long to initially wait to send a notification for a group</span></span><br><span class="line"><span class="comment"># of alerts. Allows to wait for an inhibiting alert to arrive or collect</span></span><br><span class="line"><span class="comment"># more initial alerts for the same group. (Usually ~0s to few minutes.)</span></span><br><span class="line">[ <span class="attr">group_wait:</span> <span class="string">&lt;duration&gt;</span> <span class="string">|</span> <span class="string">default</span> <span class="string">=</span> <span class="string">30s</span> ]</span><br><span class="line"> </span><br><span class="line"><span class="comment"># How long to wait before sending a notification about new alerts that</span></span><br><span class="line"><span class="comment"># are added to a group of alerts for which an initial notification has</span></span><br><span class="line"><span class="comment"># already been sent. (Usually ~5m or more.)</span></span><br><span class="line">[ <span class="attr">group_interval:</span> <span class="string">&lt;duration&gt;</span> <span class="string">|</span> <span class="string">default</span> <span class="string">=</span> <span class="string">5m</span> ]</span><br><span class="line"> </span><br><span class="line"><span class="comment"># How long to wait before sending a notification again if it has already</span></span><br><span class="line"><span class="comment"># been sent successfully for an alert. (Usually ~3h or more).</span></span><br><span class="line"><span class="comment"># Note that this parameter is implicitly bound by Alertmanager&#x27;s</span></span><br><span class="line"><span class="comment"># `--data.retention` configuration flag. Notifications will be resent after either</span></span><br><span class="line"><span class="comment"># repeat_interval or the data retention period have passed, whichever</span></span><br><span class="line"><span class="comment"># occurs first. `repeat_interval` should not be less than `group_interval`.</span></span><br><span class="line">[ <span class="attr">repeat_interval:</span> <span class="string">&lt;duration&gt;</span> <span class="string">|</span> <span class="string">default</span> <span class="string">=</span> <span class="string">4h</span> ]</span><br></pre></td></tr></table></figure><h2 id="通知时机举例"><a href="#通知时机举例" class="headerlink" title="通知时机举例"></a>通知时机举例</h2><p>首先假设alertmanager配置如下：</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></pre></td><td class="code"><pre><span class="line"><span class="attr">route:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">receiver:</span> <span class="string">&quot;ice&quot;</span></span><br><span class="line">    <span class="attr">group_wait:</span> <span class="string">&quot;10s&quot;</span></span><br><span class="line">    <span class="attr">group_interval:</span> <span class="string">&quot;2m&quot;</span></span><br><span class="line">    <span class="attr">repeat_interval:</span> <span class="string">&quot;5m&quot;</span></span><br><span class="line">    <span class="attr">match:</span></span><br><span class="line">      <span class="attr">groupTag:</span> <span class="string">&quot;ice&quot;</span></span><br><span class="line"><span class="attr">receivers:</span></span><br><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">&quot;ice&quot;</span></span><br><span class="line">  <span class="attr">webhook_configs:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">send_resolved:</span> <span class="literal">true</span></span><br><span class="line">    <span class="attr">url:</span> <span class="string">&quot;http://172.16.182.162:8080/ice&quot;</span></span><br></pre></td></tr></table></figure><h3 id="关键时机"><a href="#关键时机" class="headerlink" title="关键时机"></a>关键时机</h3><p>下图说明了当按照上面配置时，alertmanager的通知时机</p><p><img src="https://file.chaofan.xyz/blog/202401212120745.png"></p><h2 id="工作原理"><a href="#工作原理" class="headerlink" title="工作原理"></a>工作原理</h2><p>Alertmanager 在收到一条新的告警之后，会等待 group_wait 时间，对这条新的告警做一些分组、更新、静默的操作。当第一条告警经过 group_wait 时间之后，Alertmanager 会每隔 group_interval 时间检查一次这条告警，判断是否需要对这条告警进行一些操作，当 Alertmanager 经过 n 次 group_interval 的检查后，n*group_interval 恰好大于 repeat_interval 的时候，Alertmanager 才会将这条告警再次发送给对应的 receiver。</p><p>在该组的alert第一次被发送后，该组会进入睡眠&#x2F;唤醒周期，睡眠周期将持续group_interval时间，在睡眠状态下该group不会进行任何发送告警的操作（但会插入&#x2F;更新(根据fingerprint)group中的内容），睡眠结束后进入唤醒状态，然后检查是否需要发送新的alert或者重复已发送的alert(resolved类型的alert在发送完后会从group中剔除)。这就是group_interval的作用。</p><p>聚合组在每次唤醒才会检查上一次发送alert是否已经超过repeat_interval时间，如果超过则再次发送该告警。因此repeat_interval并不代表告警的实际重复间隔，因为在第一次发送告警的repeat_interval时间后，聚合组可能还处在睡眠状态，所以实际的告警间隔应该大于repeat_interval且小于repeat_interval+group_interval。因此实际生产中group_interval值不可设得太大。</p><h3 id="例外情况"><a href="#例外情况" class="headerlink" title="例外情况"></a>例外情况</h3><p>理想情况下，我们总是期望每个alert都有对应的resolved, 每个resolved也能找到每个对应的alert, 但是有时会有例外情况</p><h4 id="有些resolved-alert没有对应的firing-alert？"><a href="#有些resolved-alert没有对应的firing-alert？" class="headerlink" title="有些resolved alert没有对应的firing alert？"></a>有些resolved alert没有对应的firing alert？</h4><p>为什么有些resolved alert没有对应的firing alert，因为这些firing alert发送给alertmanager时其所在的group恰好处在睡眠状态下，而其对应的resolved消息也在同一睡眠周期内被发送给alertmanager，接收到resolved消息后，group将其对应的firing消息覆盖，因此在唤醒时就只接收到了resolved消息。</p><h4 id="有些的firing-alert没有对应的resolved-alert？"><a href="#有些的firing-alert没有对应的resolved-alert？" class="headerlink" title="有些的firing alert没有对应的resolved alert？"></a>有些的firing alert没有对应的resolved alert？</h4><p>同理，为什么有些的firing alert没有对应的resolved alert呢？假设该firing消息发生在第n个睡眠周期，而在第n+1个睡眠周期内，该alert发生了resolved-firing-resolved…这样的状态变化，则其对应的resolved消息被n+1周期内的第二个resolved消息覆盖，因此表现为该firing alert没有对应的resolved消息。</p><h4 id="收到多条重复的resolved-alert？"><a href="#收到多条重复的resolved-alert？" class="headerlink" title="收到多条重复的resolved alert？"></a>收到多条重复的resolved alert？</h4><p>为什么有些resolved消息接收到了多条？这个问题又涉及到prometheus rule组件的一个特性，当一个alert由firing变成resolved后，该resolved alert不会只发送给alertmanager一次，而是会先保存在内存中15分钟，并且重复多次发送给alertmanager，参看如下<a href="https://github.com/prometheus/prometheus/blob/main/rules/alerting.go">代码段</a></p><figure class="highlight go"><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="comment">// resolvedRetention is the duration for which a resolved alert instance</span></span><br><span class="line"><span class="comment">// is kept in memory state and consequently repeatedly sent to the AlertManager.</span></span><br><span class="line"><span class="keyword">const</span> resolvedRetention = <span class="number">15</span> * time.Minute</span><br></pre></td></tr></table></figure><p>发送多条resolved的情况为：在第n个睡眠周期内，alertmanager接收到第一条resolved alert并将其更新进group，紧接着在唤醒时发送该group并将resolved alert从group中剔除。但在第n+1个睡眠周期内，prometheus仍然在向alertmanager发送该resolved alert，因此下次唤醒时发送的group中又带有这条resolved alert。</p><h4 id="firing-alert短时间发送了多次？"><a href="#firing-alert短时间发送了多次？" class="headerlink" title="firing alert短时间发送了多次？"></a>firing alert短时间发送了多次？</h4><p>这个容易理解，如上所述，alertmanager发送消息的单位是group，在该group被发送的下一个睡眠周期中，又有新的alert被insert到该group中，因此下一次唤醒时又发送了一次该group，表现为同一条firing alert短时间内发送了多次。</p><h3 id="最佳实践"><a href="#最佳实践" class="headerlink" title="最佳实践"></a>最佳实践</h3><p>如果需要<strong>严格的每20分钟发送一次</strong>告警，则可参考如下配置，每次group_interval唤醒后总会通知一次</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></pre></td><td class="code"><pre><span class="line"><span class="attr">group_wait:</span> <span class="string">&quot;5s&quot;</span></span><br><span class="line"><span class="attr">group_interval:</span> <span class="string">&quot;20m&quot;</span></span><br><span class="line"><span class="attr">repeat_interval:</span> [<span class="number">0</span>,<span class="string">20m)</span></span><br></pre></td></tr></table></figure><h2 id="测试程序"><a href="#测试程序" class="headerlink" title="测试程序"></a>测试程序</h2><p>可通过模拟告警发送方（如Prometheus）和接收方（receiver）来观察alertmanager配置后的实际发送情况</p><p>发送方</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><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="keyword">import</span> cn.hutool.http.HttpUtil</span><br><span class="line"><span class="keyword">import</span> cn.hutool.log.LogFactory</span><br><span class="line"><span class="keyword">import</span> cn.hutool.log.dialect.console.ConsoleColorLogFactory</span><br><span class="line"><span class="keyword">import</span> java.time.LocalDateTime</span><br><span class="line"><span class="keyword">import</span> java.util.*</span><br><span class="line"><span class="keyword">import</span> kotlin.system.exitProcess</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">  <span class="keyword">val</span> alertUrl = <span class="string">&quot;http://test1:8693/api/v1/alerts&quot;</span></span><br><span class="line">  LogFactory.setCurrentLogFactory(ConsoleColorLogFactory())</span><br><span class="line">  <span class="keyword">val</span> log = LogFactory.<span class="keyword">get</span>(<span class="string">&quot;main&quot;</span>)</span><br><span class="line"></span><br><span class="line">  <span class="keyword">var</span> index = <span class="number">0</span></span><br><span class="line">  <span class="comment">// 30s 一次执行一个定时任务</span></span><br><span class="line">  <span class="keyword">val</span> timer = Timer()</span><br><span class="line">  timer.schedule(<span class="keyword">object</span> : TimerTask() &#123;</span><br><span class="line">    <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">run</span><span class="params">()</span></span> &#123;</span><br><span class="line">      <span class="keyword">if</span> (index &gt;= <span class="number">5</span>) exitProcess(<span class="number">0</span>)</span><br><span class="line">      <span class="comment">//获取0001-01-01T00:00:00Z格式的时间</span></span><br><span class="line">      <span class="keyword">val</span> time = getISO8601Time()</span><br><span class="line">      <span class="comment">//发送告警</span></span><br><span class="line">      HttpUtil.post(alertUrl, getBody(++index, time))</span><br><span class="line">      log.info(<span class="string">&quot;send A<span class="subst">$&#123;index&#125;</span>&quot;</span>)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;, <span class="number">0</span>, <span class="number">20_000</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">getISO8601Time</span><span class="params">()</span></span>: String = LocalDateTime.now().toString().substring(<span class="number">0</span>, <span class="number">19</span>) + <span class="string">&quot;.000+08:00&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">getBody</span><span class="params">(index: <span class="type">Int</span>, time: <span class="type">String</span>)</span></span>: String = <span class="string">&quot;&quot;&quot;</span></span><br><span class="line"><span class="string">    [&#123;</span></span><br><span class="line"><span class="string">      &quot;labels&quot;: &#123;</span></span><br><span class="line"><span class="string">        &quot;alertname&quot;: &quot;A<span class="variable">$index</span>&quot;,</span></span><br><span class="line"><span class="string">        &quot;groupTag&quot;: &quot;ice&quot;,</span></span><br><span class="line"><span class="string">        &quot;instance&quot;: &quot;test1:9090&quot;,</span></span><br><span class="line"><span class="string">        &quot;job&quot;: &quot;ice&quot;,</span></span><br><span class="line"><span class="string">        &quot;severity&quot;: &quot;critical&quot;</span></span><br><span class="line"><span class="string">      &#125;,</span></span><br><span class="line"><span class="string">      &quot;annotations&quot;: &#123;</span></span><br><span class="line"><span class="string">        &quot;description&quot;: &quot;test<span class="variable">$index</span>&quot;,</span></span><br><span class="line"><span class="string">        &quot;summary&quot;: &quot;test<span class="variable">$index</span>&quot;</span></span><br><span class="line"><span class="string">      &#125;,</span></span><br><span class="line"><span class="string">      &quot;startsAt&quot;: &quot;<span class="variable">$time</span>&quot;,</span></span><br><span class="line"><span class="string">      &quot;endsAt&quot;: &quot;2023-09-01T10:59:00.000+08:00&quot;,</span></span><br><span class="line"><span class="string">      &quot;generatorURL&quot;: &quot;http://test1:9090/graph?g0.expr=up&amp;g0.tab=1&quot;</span></span><br><span class="line"><span class="string">    &#125;]</span></span><br><span class="line"><span class="string">  &quot;&quot;&quot;</span>.trimIndent()</span><br></pre></td></tr></table></figure><p>接收方</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><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">import</span> cn.hutool.core.date.DateUtil</span><br><span class="line"><span class="keyword">import</span> cn.hutool.core.lang.ConsoleTable</span><br><span class="line"><span class="keyword">import</span> org.slf4j.Logger</span><br><span class="line"><span class="keyword">import</span> org.slf4j.LoggerFactory</span><br><span class="line"><span class="keyword">import</span> org.springframework.boot.autoconfigure.SpringBootApplication</span><br><span class="line"><span class="keyword">import</span> org.springframework.boot.runApplication</span><br><span class="line"><span class="keyword">import</span> org.springframework.web.bind.<span class="keyword">annotation</span>.GetMapping</span><br><span class="line"><span class="keyword">import</span> org.springframework.web.bind.<span class="keyword">annotation</span>.RequestBody</span><br><span class="line"><span class="keyword">import</span> org.springframework.web.bind.<span class="keyword">annotation</span>.RequestMapping</span><br><span class="line"><span class="keyword">import</span> org.springframework.web.bind.<span class="keyword">annotation</span>.RestController</span><br><span class="line"><span class="keyword">import</span> kotlin.concurrent.thread</span><br><span class="line"><span class="keyword">import</span> kotlin.concurrent.timer</span><br><span class="line"></span><br><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">ReceiverApplication</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">main</span><span class="params">(args: <span class="type">Array</span>&lt;<span class="type">String</span>&gt;)</span></span> &#123;</span><br><span class="line">  runApplication&lt;ReceiverApplication&gt;(*args)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">AlertController</span> &#123;</span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">val</span> log: Logger = LoggerFactory.getLogger(<span class="string">&quot;alert&quot;</span>)</span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">val</span> table: ConsoleTable = ConsoleTable.create()</span><br><span class="line">    .addHeader(<span class="string">&quot;时间&quot;</span>, <span class="string">&quot;间隔&quot;</span>, <span class="string">&quot;活跃告警&quot;</span>, <span class="string">&quot;解除告警&quot;</span>, <span class="string">&quot;group_interval&quot;</span>, <span class="string">&quot;repeat_interval&quot;</span>)</span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">val</span> globalTimer = DateUtil.timer()</span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">val</span> row = mutableMapOf&lt;String, String&gt;()</span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">var</span> groupThread: Thread = thread(name = <span class="string">&quot;group_interval&quot;</span>, start = <span class="literal">false</span>) &#123;</span><br><span class="line">    timer(name = <span class="string">&quot;group&quot;</span>, period = <span class="number">120_000</span>) &#123;</span><br><span class="line">      row[<span class="string">&quot;group_interval&quot;</span>] = <span class="string">&quot;1&quot;</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">var</span> repeatThread: Thread = thread(name = <span class="string">&quot;repeat_interval&quot;</span>, start = <span class="literal">false</span>) &#123;</span><br><span class="line">    timer(name = <span class="string">&quot;repeat&quot;</span>, period = <span class="number">300_000</span>) &#123;</span><br><span class="line">      row[<span class="string">&quot;repeat_interval&quot;</span>] = <span class="string">&quot;1&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="meta">@GetMapping(<span class="string">&quot;/print&quot;</span>)</span></span><br><span class="line">  <span class="function"><span class="keyword">fun</span> <span class="title">print</span><span class="params">()</span></span> = table.toString()</span><br><span class="line"></span><br><span class="line">  <span class="meta">@RequestMapping(<span class="string">&quot;ice&quot;</span>)</span></span><br><span class="line">  <span class="function"><span class="keyword">fun</span> <span class="title">webhook</span><span class="params">(<span class="meta">@RequestBody</span> notify: <span class="type">Notify</span>)</span></span> &#123;</span><br><span class="line">    log.info(<span class="string">&quot;receiver: <span class="subst">$&#123;notify.alerts.size&#125;</span> alerts&quot;</span>)</span><br><span class="line">    <span class="keyword">if</span> (groupThread.state == Thread.State.NEW) &#123;</span><br><span class="line">      groupThread.start()</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (repeatThread.state == Thread.State.NEW) &#123;</span><br><span class="line">      repeatThread.start()</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">val</span> actives = mutableListOf&lt;String&gt;()</span><br><span class="line">    <span class="keyword">val</span> releases = mutableListOf&lt;String&gt;()</span><br><span class="line">    notify.alerts.forEach &#123;</span><br><span class="line">      <span class="keyword">when</span> (it.status) &#123;</span><br><span class="line">        <span class="string">&quot;firing&quot;</span> -&gt; &#123;</span><br><span class="line">          actives.add(it.labels[<span class="string">&quot;alertname&quot;</span>]!!)</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="string">&quot;resolved&quot;</span> -&gt; &#123;</span><br><span class="line">          releases.add(it.labels[<span class="string">&quot;alertname&quot;</span>]!!)</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    table.addBody(</span><br><span class="line">      DateUtil.now(),</span><br><span class="line">      globalTimer.intervalSecond().toString(),</span><br><span class="line">      actives.joinToString(<span class="string">&quot;,&quot;</span>),</span><br><span class="line">      releases.joinToString(<span class="string">&quot;,&quot;</span>),</span><br><span class="line">      row[<span class="string">&quot;group_interval&quot;</span>] ?: <span class="string">&quot;&quot;</span>,</span><br><span class="line">      row[<span class="string">&quot;repeat_interval&quot;</span>] ?: <span class="string">&quot;&quot;</span>,</span><br><span class="line">    )</span><br><span class="line">    row.remove(<span class="string">&quot;group_interval&quot;</span>)</span><br><span class="line">    row.remove(<span class="string">&quot;repeat_interval&quot;</span>)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>配置好之后，reload alertmanager,</p><p>然后启动接收程序，最后启动发送程序，发送10个告警过后暂停一段时间，然后再发送一个告警，观察日志输出情况</p><h2 id="最后"><a href="#最后" class="headerlink" title="最后"></a>最后</h2><p>本文暂未考虑alertmanager集群的情况，仅考虑单个alertmanager实例</p>]]>
    </content>
    <id>https://chaofan.xyz/posts/4a58f7b9/</id>
    <link href="https://chaofan.xyz/posts/4a58f7b9/"/>
    <published>2024-01-21T21:14:42.000Z</published>
    <summary>
      <![CDATA[<p><a href="https://prometheus.io/docs/alerting/latest/alertmanager/">Alertmanager</a>可以接收<a href="https://prometheus.io/">Prometheus</a>等客户端发来的告警，之后通过分组、去重等处理，将它们通过路由发送给正确的接收器。</p>]]>
    </summary>
    <title>alertmanager通知时机</title>
    <updated>2026-08-09T09:24:32.640Z</updated>
  </entry>
  <entry>
    <author>
      <name>chaofan</name>
      <email>chaofanxy@outlook.com</email>
    </author>
    <category term="云原生" scheme="https://chaofan.xyz/categories/%E4%BA%91%E5%8E%9F%E7%94%9F/"/>
    <category term="Java" scheme="https://chaofan.xyz/tags/Java/"/>
    <category term="可观测性" scheme="https://chaofan.xyz/tags/%E5%8F%AF%E8%A7%82%E6%B5%8B%E6%80%A7/"/>
    <category term="Prometheus" scheme="https://chaofan.xyz/tags/Prometheus/"/>
    <content>
      <![CDATA[<span id="more"></span><h2 id="Prometheus"><a href="#Prometheus" class="headerlink" title="Prometheus"></a>Prometheus</h2><p>Prometheus是一个<strong>开源监控解决方案</strong>，用于收集和聚合指标作为时间序列数据。是继<a href="https://kubernetes.io/">Kubernetes</a>之后第二个<a href="https://www.cncf.io/">CNCF</a>托管项目</p><h3 id="特点"><a href="#特点" class="headerlink" title="特点"></a>特点</h3><p>普罗米修斯的主要特点是：</p><ul><li>多维<a href="https://prometheus.io/docs/concepts/data_model/">数据模型</a>，其中时间序列数据由指标名称和键&#x2F;值对标识</li><li>PromQL，一种<a href="https://prometheus.io/docs/prometheus/latest/querying/basics/">灵活的查询语言</a></li><li>不依赖分布式存储；单个服务器节点是自治的</li><li>时间序列收集通过 HTTP 上的 Pull 模型进行</li><li>通过中间网关支持<a href="https://prometheus.io/docs/instrumenting/pushing/">推送时间序列</a></li><li>通过服务发现或静态配置发现Target</li><li>多种图形和仪表板支持</li></ul><h3 id="什么是指标？"><a href="#什么是指标？" class="headerlink" title="什么是指标？"></a>什么是指标？</h3><p>指标是外行术语中的数值测量。术语时间序列是指随时间变化的记录。用户想要测量的内容因应用程序而异。对于 Web 服务器来说，它可能是请求时间；对于数据库，它可以是活动连接或活动查询的数量等。</p><p>指标在理解应用程序为何以某种方式运行方面发挥着重要作用。假设您正在运行一个 Web 应用程序并发现它很慢。要了解您的应用程序发生了什么，您将需要一些信息。例如，当请求数量较多时，应用程序可能会变慢。如果您有请求计数指标，则可以确定原因并增加处理负载的服务器数量。</p><p><a href="https://prometheus.io/docs/introduction/overview/">更多信息</a></p><p>下面将会介绍常见开发语言如果集成到Prometheus监控体系中。</p><h2 id="Java"><a href="#Java" class="headerlink" title="Java"></a>Java</h2><p>在Java世界里，所有应用事实上可以简单的划分为两类：Spring应用和非Spring应用。Spring已经成为了事实上的Jakarta EE(Java EE)标准，所以本节会分别介绍Spring应用和非Spring应用如何集成Prometheus指标。</p><h3 id="Micrometer"><a href="#Micrometer" class="headerlink" title="Micrometer"></a>Micrometer</h3><p>首先需要说明一下就是<a href="https://micrometer.io/">Micrometer</a>，Micrometer是一个在JVM-Based应用中的一个供应商中立的应用可观测性门面(facade)，类似于slf4j。Micrometer支持常见的可观测性系统，包括Prometheus。</p><h4 id="基本概念"><a href="#基本概念" class="headerlink" title="基本概念"></a>基本概念</h4><p><strong>Registry</strong></p><p>每个应用都有一个MeterRegistry对象。是所有指标的注册表。</p><p><strong>CompositeMeterRegistry</strong></p><p>通过CompositeMeterRegistry，可以同时添加多个注册表，允许发布到多个监控系统</p><p><strong>globalRegistry</strong></p><p>一个静态全局注册表</p><p><strong>Meter</strong></p><p>监控中不同类型的指标，包括<code>Timer</code>、<code>Counter</code>、<code>Gauge</code>、<code>DistributionSummary</code>、<code>LongTaskTimer</code>、<code>FunctionCounter</code>、<code>FunctionTimer</code>和<code>TimeGauge</code>称作Meter。</p><h4 id="命名约定"><a href="#命名约定" class="headerlink" title="命名约定"></a>命名约定</h4><p>由于不同监控系统有不同的指标名约定，Micrometer建议使用小数点分割，然后会转换为目标监控系统推荐的命名约定，请看下面的例子：</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">registry.timer(<span class="string">&quot;http.server.requests&quot;</span>);</span><br></pre></td></tr></table></figure><p>在不同的监控系统中，会转换为推荐的名称：</p><ol><li>Prometheus - <code>http_server_requests_duration_seconds</code></li><li>Atlas - <code>httpServerRequests</code></li><li>Graphite - <code>http.server.requests</code></li><li>InfluxDB - <code>http_server_requests</code></li></ol><h3 id="SpringBoot-Micrometer"><a href="#SpringBoot-Micrometer" class="headerlink" title="SpringBoot &amp; Micrometer"></a>SpringBoot &amp; Micrometer</h3><p>首先创建一个SpringBoot项目，然后添加依赖</p><figure class="highlight groovy"><table><tr><td class="gutter"><pre><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">dependencies &#123;</span><br><span class="line">  implementation <span class="string">&#x27;org.springframework.boot:spring-boot-starter-actuator&#x27;</span></span><br><span class="line">  implementation <span class="string">&#x27;io.micrometer:micrometer-registry-prometheus&#x27;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>接下来编辑<code>application.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></pre></td><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">application:</span></span><br><span class="line">    <span class="attr">name:</span> <span class="string">JavaMetricsDemo</span></span><br><span class="line"></span><br><span class="line"><span class="attr">management:</span></span><br><span class="line">  <span class="attr">endpoints:</span></span><br><span class="line">    <span class="attr">web:</span></span><br><span class="line">      <span class="attr">exposure:</span></span><br><span class="line">        <span class="attr">include:</span> <span class="string">prometheus</span></span><br><span class="line">  <span class="attr">metrics:</span></span><br><span class="line">    <span class="attr">tags:</span></span><br><span class="line">      <span class="attr">application:</span> <span class="string">$&#123;spring.application.name&#125;</span></span><br></pre></td></tr></table></figure><p>首先，定义了应用的名称为<code>JavaMetricsDemo</code>，然后在management中开启prometheus端点暴露，接下来通过mertris配置给所有指标添加了一个自定义tag<code>application=JavaMetricsDemo</code>，方便区分</p><p>然后直接启动应用，访问<code>http://localhost:8080/actuator/prometheus</code>，即可看到默认情况下会暴露一些常见指标，jvm，tomcat，logback等。</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></pre></td><td class="code"><pre><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_">DemoController</span> &#123;</span><br><span class="line"></span><br><span class="line">  <span class="meta">@Timed(value = &quot;foo.time&quot;, description = &quot;Time taken to return foo&quot;, histogram = true)</span></span><br><span class="line">  <span class="meta">@RequestMapping</span></span><br><span class="line">  <span class="keyword">public</span> String <span class="title function_">foo</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;foo&quot;</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>我们创建了一个DemoController，然后定义了一个返回字符串的接口，注意我们在方法上面添加了**@Timed**注解，然后重新启动应用，观察指标变化</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"># HELP http_server_requests_seconds  </span><br><span class="line"># TYPE http_server_requests_seconds summary</span><br><span class="line">http_server_requests_seconds_count&#123;application=&quot;JavaMetricsDemo&quot;,error=&quot;RuntimeException&quot;,exception=&quot;RuntimeException&quot;,method=&quot;GET&quot;,outcome=&quot;SERVER_ERROR&quot;,status=&quot;500&quot;,uri=&quot;/&quot;,&#125; 2.0</span><br><span class="line">http_server_requests_seconds_sum&#123;application=&quot;JavaMetricsDemo&quot;,error=&quot;RuntimeException&quot;,exception=&quot;RuntimeException&quot;,method=&quot;GET&quot;,outcome=&quot;SERVER_ERROR&quot;,status=&quot;500&quot;,uri=&quot;/&quot;,&#125; 0.0217194</span><br><span class="line">http_server_requests_seconds_count&#123;application=&quot;JavaMetricsDemo&quot;,error=&quot;none&quot;,exception=&quot;none&quot;,method=&quot;GET&quot;,outcome=&quot;SUCCESS&quot;,status=&quot;200&quot;,uri=&quot;/&quot;,&#125; 3.0</span><br><span class="line">http_server_requests_seconds_sum&#123;application=&quot;JavaMetricsDemo&quot;,error=&quot;none&quot;,exception=&quot;none&quot;,method=&quot;GET&quot;,outcome=&quot;SUCCESS&quot;,status=&quot;200&quot;,uri=&quot;/&quot;,&#125; 0.0175536</span><br><span class="line"># HELP http_server_requests_seconds_max  </span><br><span class="line"># TYPE http_server_requests_seconds_max gauge</span><br><span class="line">http_server_requests_seconds_max&#123;application=&quot;JavaMetricsDemo&quot;,error=&quot;RuntimeException&quot;,exception=&quot;RuntimeException&quot;,method=&quot;GET&quot;,outcome=&quot;SERVER_ERROR&quot;,status=&quot;500&quot;,uri=&quot;/&quot;,&#125; 0.0203117</span><br><span class="line">http_server_requests_seconds_max&#123;application=&quot;JavaMetricsDemo&quot;,error=&quot;none&quot;,exception=&quot;none&quot;,method=&quot;GET&quot;,outcome=&quot;SUCCESS&quot;,status=&quot;200&quot;,uri=&quot;/&quot;,&#125; 0.0114682</span><br></pre></td></tr></table></figure><p>由此我们可以知道，通过添加**@Timed**，可以获取接口响应时间相关指标，包含最大值，请求次数等。通过上述指标，可能会对服务健康提供一定帮助，也可以通过指标的tag精确到每个不同的接口请求。</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></pre></td><td class="code"><pre><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_">DemoController</span> &#123;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">final</span> MeterRegistry meterRegistry;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">public</span> <span class="title function_">JavaMetricsApplication</span><span class="params">(MeterRegistry meterRegistry)</span> &#123;</span><br><span class="line">    <span class="built_in">this</span>.meterRegistry = meterRegistry;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="meta">@PostConstruct</span></span><br><span class="line">  <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">init</span><span class="params">()</span> &#123;</span><br><span class="line">    Counter.builder(<span class="string">&quot;foo.errors&quot;</span>).description(<span class="string">&quot;Number of errors&quot;</span>).register(meterRegistry);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="meta">@RequestMapping</span></span><br><span class="line">  <span class="keyword">public</span> String <span class="title function_">foo</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (Math.random() &gt; <span class="number">0.5</span>) &#123;</span><br><span class="line">      <span class="type">Counter</span> <span class="variable">counter</span> <span class="operator">=</span> meterRegistry.counter(<span class="string">&quot;foo.errors&quot;</span>);</span><br><span class="line">      counter.increment();</span><br><span class="line">      <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(<span class="string">&quot;oops&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="string">&quot;foo&quot;</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>通过IOC容器注入一个MeterRegistry，这就是上文说的每个应用的Registry对象，我们一般不需要手动创建，直接注入使用即可。</p><p>然后我们在init段中声明一个Counter类型的指标，这个过程建议单独放到一起，在应用启动时候注册到全局的Registry即可，然后我们获取一个叫<code>foo.errors</code>的Counter，用来统计错误次数，每当程序发生异常，就将次数加一。</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></pre></td><td class="code"><pre><span class="line"># HELP foo_errors_total Number of errors</span><br><span class="line"># TYPE foo_errors_total counter</span><br><span class="line">foo_errors_total&#123;application=&quot;JavaMetricsDemo&quot;,&#125; 3.0</span><br></pre></td></tr></table></figure><p>可以看到我们新增的指标已经添加上来了</p><blockquote><p><strong>指标名和代码中的不同？</strong></p><p>这里按照micrometer约定，因为micrometer是一个厂商中立的api，所以为了保持统一性，代码中所有指标统一用点分隔，在具体的provider中会自动转换成目标推荐的格式，这里就按照Prometheus的格式转换成了下划线，又因为是counter类型，所以最后添加了_total后缀。</p><p>总之，在代码中统一使用点分隔即可</p></blockquote><p>到这里，演示了如何使用micrometer在一个Java应用中暴露指标接口和自定义指标，其他类型参考Counter用法即可。</p><h3 id="Prometheus-client-java"><a href="#Prometheus-client-java" class="headerlink" title="Prometheus client_java"></a>Prometheus client_java</h3><p>偶然发现Prometheus官方提供的clinet库从之前的simple_client换成了<a href="https://github.com/prometheus/client_java">client_java</a>，并且释出了1.0版本，这里也简单使用一下</p><h4 id="快速开始"><a href="#快速开始" class="headerlink" title="快速开始"></a>快速开始</h4><p>详情参考 <a href="https://prometheus.github.io/client_java/getting-started/quickstart/">quickstart</a></p><p>首先引入依赖</p><figure class="highlight groovy"><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">implementation <span class="string">&#x27;io.prometheus:prometheus-metrics-core:1.0.0&#x27;</span></span><br><span class="line">implementation <span class="string">&#x27;io.prometheus:prometheus-metrics-instrumentation-jvm:1.0.0&#x27;</span></span><br><span class="line">implementation <span class="string">&#x27;io.prometheus:prometheus-metrics-exporter-httpserver:1.0.0&#x27;</span></span><br></pre></td></tr></table></figure><ul><li><code>prometheus-metrics-core</code>是指标库。</li><li><code>prometheus-metrics-instrumentation-jvm</code>提供开箱即用的 JVM 指标。</li><li><code>prometheus-metrics-exporter-httpserver</code>是一个用于公开 Prometheus 指标的独立 HTTP 服务器。</li></ul><blockquote><p>如果项目已经使用Servlet等，可以使用<code>prometheus-exporter-servlet-jakarta</code></p></blockquote><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> io.prometheus.metrics.core.metrics.Counter;</span><br><span class="line"><span class="keyword">import</span> io.prometheus.metrics.exporter.httpserver.HTTPServer;</span><br><span class="line"><span class="keyword">import</span> io.prometheus.metrics.instrumentation.jvm.JvmMetrics;</span><br><span class="line"><span class="keyword">import</span> java.io.IOException;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">App</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, IOException &#123;</span><br><span class="line">        JvmMetrics.builder().register(); <span class="comment">// initialize the out-of-the-box JVM metrics</span></span><br><span class="line">        <span class="type">Counter</span> <span class="variable">counter</span> <span class="operator">=</span> Counter.builder()</span><br><span class="line">                .name(<span class="string">&quot;my_count_total&quot;</span>)</span><br><span class="line">                .help(<span class="string">&quot;example counter&quot;</span>)</span><br><span class="line">                .labelNames(<span class="string">&quot;status&quot;</span>)</span><br><span class="line">                .register();</span><br><span class="line">        counter.labelValues(<span class="string">&quot;ok&quot;</span>).inc();</span><br><span class="line">        counter.labelValues(<span class="string">&quot;ok&quot;</span>).inc();</span><br><span class="line">        counter.labelValues(<span class="string">&quot;error&quot;</span>).inc();</span><br><span class="line">        <span class="type">HTTPServer</span> <span class="variable">server</span> <span class="operator">=</span> HTTPServer.builder()</span><br><span class="line">                .port(<span class="number">9400</span>)</span><br><span class="line">                .buildAndStart();</span><br><span class="line">        System.out.println(<span class="string">&quot;HTTPServer listening on port http://localhost:&quot;</span> + server.getPort() + <span class="string">&quot;/metrics&quot;</span>);</span><br><span class="line">        Thread.currentThread().join(); <span class="comment">// sleep forever</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这是首先初始化了<code>prometheus-metrics-instrumentation-jvm</code>提供的自带的JVM指标，可以看到这里并没有<strong>Registry</strong>声明，因为通过<a href="https://prometheus.github.io/client_java/getting-started/registry/#registering-a-metrics-with-the-default-registry">这里</a>可以看到<code>client_java</code>默认隐式使用了一个全局的Registry，并且也推荐只使用默认实现。</p><p>然后通过CounterBuilder创建了一个Counter指标，并且设置了几个样本值，最后在9400端口启动了一个Prometheus指标端点。</p><blockquote><p>分析源码可以发现<code>prometheus-metrics-exporter-httpserver</code>默认使用了jdk HttpServer实现</p></blockquote><p>然后需要解释一下这里的<code>labelNames</code>和<code>labelValues</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></pre></td><td class="code"><pre><span class="line"><span class="type">Counter</span> <span class="variable">counter</span> <span class="operator">=</span> Counter.builder()</span><br><span class="line">                .name(<span class="string">&quot;my_count_total&quot;</span>)</span><br><span class="line">                .help(<span class="string">&quot;example counter&quot;</span>)</span><br><span class="line">                .labelNames(<span class="string">&quot;status&quot;</span>, <span class="string">&quot;path&quot;</span>)</span><br><span class="line">                .register();</span><br><span class="line">        counter.labelValues(<span class="string">&quot;ok&quot;</span>, <span class="string">&quot;/foo&quot;</span>).inc();</span><br><span class="line">        counter.labelValues(<span class="string">&quot;ok&quot;</span>, <span class="string">&quot;/bar&quot;</span>).inc();</span><br><span class="line">        counter.labelValues(<span class="string">&quot;error&quot;</span>, <span class="string">&quot;/error&quot;</span>).inc();</span><br></pre></td></tr></table></figure><p>更多概念一文也无法讲清楚，若有需要推荐查看官网<a href="https://prometheus.github.io/client_java/">原文</a></p><h4 id="Spring"><a href="#Spring" class="headerlink" title="Spring"></a>Spring</h4><p>目前client_java在2023年9月27日才发布1.0版本，所以目前官方还是推荐使用<a href="#Micrometer">Micrometer</a>方案。</p><p>非Spring项目中可考虑使用client_java和Micrometer皆可</p><h2 id="其他语言"><a href="#其他语言" class="headerlink" title="其他语言"></a>其他语言</h2><p>Prometheus提供了多种语言的SDK用来对应用可观测性指标收集，目前，官方支持的有：</p><ul><li><a href="https://github.com/prometheus/client_golang">Go</a></li><li><a href="https://github.com/prometheus/client_java">Java or Scala</a></li><li><a href="https://github.com/prometheus/client_python">Python</a></li><li><a href="https://github.com/prometheus/client_ruby">Ruby</a></li><li><a href="https://github.com/prometheus/client_rust">Rust</a></li></ul><p>非官方支持：</p><ul><li><a href="https://github.com/aecolley/client_bash">Bash</a></li><li><a href="https://github.com/digitalocean/prometheus-client-c">C</a></li><li><a href="https://github.com/jupp0r/prometheus-cpp">C++</a></li><li><a href="https://github.com/deadtrickster/prometheus.cl">Common Lisp</a></li><li><a href="https://github.com/tentaclelabs/prometheus_client">Dart</a></li><li><a href="https://github.com/marcobreveglieri/prometheus-client-delphi">Delphi</a></li><li><a href="https://github.com/deadtrickster/prometheus.ex">Elixir</a></li><li><a href="https://github.com/deadtrickster/prometheus.erl">Erlang</a></li><li><a href="https://github.com/fimad/prometheus-haskell">Haskell</a></li><li><a href="https://github.com/knyar/nginx-lua-prometheus">Lua</a> for Nginx</li><li><a href="https://github.com/tarantool/metrics">Lua</a> for Tarantool</li><li><a href="https://github.com/prometheus-net/prometheus-net">.NET &#x2F; C#</a></li><li><a href="https://github.com/siimon/prom-client">Node.js</a></li><li><a href="https://github.com/mirage/prometheus">OCaml</a></li><li><a href="https://metacpan.org/pod/Net::Prometheus">Perl</a></li><li><a href="https://github.com/promphp/prometheus_client_php">PHP</a></li><li><a href="https://github.com/cfmack/pRometheus">R</a></li></ul>]]>
    </content>
    <id>https://chaofan.xyz/posts/49b1cc7e/</id>
    <link href="https://chaofan.xyz/posts/49b1cc7e/"/>
    <published>2023-09-30T00:48:32.000Z</published>
    <summary>
      <![CDATA[<span id="more"></span>

<h2 id="Prometheus"><a href="#Prometheus" class="headerlink" title="Prometheus"></a>Prometheus</h2><p>Prometheus是一个]]>
    </summary>
    <title>使用Prometheus监控应用指标</title>
    <updated>2026-08-09T09:24:32.640Z</updated>
  </entry>
  <entry>
    <author>
      <name>chaofan</name>
      <email>chaofanxy@outlook.com</email>
    </author>
    <category term="云原生" scheme="https://chaofan.xyz/categories/%E4%BA%91%E5%8E%9F%E7%94%9F/"/>
    <category term="可观测性" scheme="https://chaofan.xyz/tags/%E5%8F%AF%E8%A7%82%E6%B5%8B%E6%80%A7/"/>
    <category term="云原生" scheme="https://chaofan.xyz/tags/%E4%BA%91%E5%8E%9F%E7%94%9F/"/>
    <content>
      <![CDATA[<p><a href="https://opentelemetry.io/">OpenTelemetry</a> 是一个开源的可观测性框架，由云原生基金会(<a href="https://www.cncf.io/">CNCF</a>)托管。它是 <a href="https://opencensus.io/">OpenCensus</a> 和 <a href="https://opentracing.io/">OpenTracing</a> 项目的合并。旨在为所有类型的可观测信号(如<strong>跟踪（Traces）</strong>、<strong>指标（Metrics）<strong>和</strong>日志（Logs）</strong>)提供单一标准。</p><span id="more"></span><h2 id="OpenTelemetry"><a href="#OpenTelemetry" class="headerlink" title="OpenTelemetry"></a>OpenTelemetry</h2><p>OpenTelemetry  [təˈlemətri]指定了如何收集可观测数据并将其发送到后端平台。通过提供通用的数据格式和 API, OpenTelemetry 使组织更容易共享和重用观测数据，从而与各种可观测性工具和平台集成。</p><p>OpenTelemetry 架构促进了<strong>灵活性</strong>、<strong>互操作性</strong>和<strong>可扩展性</strong>，使开发人员能够采用满足其特定需求和环境的可观测性实践。</p><h3 id="Dapper"><a href="#Dapper" class="headerlink" title="Dapper"></a>Dapper</h3><p>谷歌于2010年发表的<a href="https://static.googleusercontent.com/media/research.google.com/zh-CN//archive/papers/dapper-2010-1.pdf">Google Dapper</a>，介绍了他们的分布式跟踪技术。</p><p>本文首先通过网络搜索举例。处理一个通用搜索查询可能需要数千台机器和刷多不同的服务，而用户对延迟很敏感，可能是某个子系统出现异常。但是工程师查看整体延迟只能知道可能存在问题，但是无法猜测那个服务有问题，也无法猜测为什么有问题。这里主要有下面几个问题：</p><ul><li>工程师可能不知道在使用哪个服务。新的服务和功能总是在快速变化。</li><li>工程师不是每个内部服务的领域专家。每个服务都是由不同的团队构建和维护的。</li><li>服务和机器可能多个人在同时使用，因此性能问题可能是另一个应用程序的行为。</li></ul><p>上述场景对Dapper提出了两个基本要求：<strong>全面部署</strong>和<strong>持续监控</strong>。即使系统的一小部分没有受到监控，追踪的基础设施可用性也会受到影响。而且通常情况下，异常或其他值得注意的系统行为很难或不可能重现。这两个要求产生了下面三个具体的设计目标：</p><ul><li><strong>低开销</strong>：追踪系统对正在运行的服务性能影响应该可以忽略不计。 在一些高度优化的服务中，即使很小的监控开销也很容易引起注意，并可能迫使部署团队关闭追踪系统。</li><li><strong>应用程序级透明</strong>：程序员不需要知道追踪系统。 依赖于应用程序开发人员的积极协作才能运行的追踪基础设施是极其脆弱的，并且经常由于检测工具的错误或遗漏而被破坏，因此违反了普遍性的要求。 这在像我们这样快节奏的开发环境中尤为重要。</li><li><strong>可扩展性</strong>：它需要应对至少在未来几年内服务和集群规模的增长。</li></ul><p>另一个设计目标是追踪数据在生成后可快速用于分析：<strong>最好在一分钟内</strong>。</p><h4 id="分布式跟踪"><a href="#分布式跟踪" class="headerlink" title="分布式跟踪"></a>分布式跟踪</h4><p>基本概念：</p><ul><li>追踪树（Trace Trees）：叶子节点是每一个span，一棵树表示一个完整的请求过程。</li><li>跨度（spans）：最小的追踪单元。span 可以是远程过程调用(RPC)、数据库查询或进程内函数调用。</li></ul><p><img src="https://file.chaofan.xyz/blog/202309282256864.png" alt="Dapper 追踪树中五个跨度 spans 之间的因果关系和时间关系"></p><p>其中，每棵树都有一个唯一的TraceId，用于区分不同的Trace。边表示span之间的因果关系。</p><p>span拥有下面的属性：</p><ul><li>Span ID</li><li>Parent ID</li><li>开始时间和结束时间</li><li>RPC时间数据</li><li>应用自定义标识数据annotations</li></ul><p>没有父节点的Span是根Span (root span)。</p><h3 id="OpenTelemetry类型"><a href="#OpenTelemetry类型" class="headerlink" title="OpenTelemetry类型"></a>OpenTelemetry类型</h3><p>OpenTelemetry 支持不同的遥测数据类型：</p><ul><li><strong>OpenTelemetry Trace</strong> 表示跨多个组件和服务的请求或操作的执行路径。它们提供详细的时间和上下文信息，允许开发人员了解请求流并确定性能瓶颈。</li><li><strong>OpenTelemetry Metrics</strong> 是对系统行为或资源利用率的定量测量。它们有助于监控和分析一段时间内的性能，并可用于警报、容量规划和趋势分析。</li><li><strong>OpenTelemetry Logs</strong> 包含有关应用程序中发生的事件、错误和活动的结构化或非结构化文本信息。它们对于调试、审计和故障排除非常有用。</li></ul><h3 id="OpenTelemetry架构"><a href="#OpenTelemetry架构" class="headerlink" title="OpenTelemetry架构"></a>OpenTelemetry架构</h3><h4 id="术语表"><a href="#术语表" class="headerlink" title="术语表"></a>术语表</h4><ul><li><strong>OpenTelemetry API</strong> 是一个编程接口，您可以使用它来检测代码以收集遥测数据，如跟踪、指标和日志。</li></ul><p>OpenTelemetry API是OpenTelemetry项目的核心，它定义了一组抽象的接口和规范，用于记录应用程序性能数据、创建度量指标和传递上下文信息。这些接口和规范允许开发者编写自己的代码以收集、导出和处理性能数据。</p><ul><li><strong>OpenTelemetry SDK</strong> 是 OpenTelemetry API 的官方实现，用于处理和将收集的遥测数据导出到后端。</li></ul><p>一个库和工具的集合，使开发人员能够对其应用程序进行检测并收集遥测数据以进行监控。</p><p>Otel SDK 提供了一个标准化和可扩展的框架，用于将 OpenTelemetry 集成到各种编程语言和环境中。</p><ul><li><strong>OpenTelemetry Instrumentation</strong>  [ˌɪnstrəmenˈteɪʃn]是流行框架和库的插件，它们使用 OpenTelemetry API 来记录重要的操作，例如 HTTP 请求、DB 查询、日志、错误等等。</li></ul><p>OpenTelemetry为常见的编程语言提供了监控和收集可遥测数据的库，支持的语言详见<a href="https://opentelemetry.io/docs/instrumentation/">https://opentelemetry.io/docs/instrumentation/</a></p><blockquote><p>三者的关系</p><p>OpenTelemetry API提供了灵活性和可扩展性，允许开发者自定义性能数据的收集和导出方式，OpenTelemetry SDK是OpenTelemetry项目的核心实现，提供了OpenTelemetry API的具体实现细节，而OpenTelemetry Instrumentation是一组用于自动化性能监控集成的库和插件</p></blockquote><ul><li><p><strong>OpenTelemetry Collector</strong> 是应用程序和后端之间的代理。它接收遥测数据，对其进行转换，然后将数据导出到可以永久存储数据的后端。Collector 还可以作为一个代理，从被监视的系统中提取遥测数据，例如，OpenTelemetry Redis 或文件系统指标。</p></li><li><p><strong>OTLP</strong> 是 SDK 和 Collector 使用的 OpenTelemetry 协议，用于将数据导出到后端或其他收集器。作为传输协议，OTLP 可以使用 gRPC (OTLP&#x2F;gRPC) 或 HTTP (OTLP&#x2F;HTTP)。</p></li><li><p><strong>OpenTelemetry Backend</strong> 负责接收、存储和分析 OpenTelemetry 收集的遥测数据。它充当数据的中央存储库或处理管道，允许您聚合、查询、可视化并从应用程序生成的遥测数据中获得见解。</p></li><li><p><strong>OTLP</strong>。<strong>OpenTelemetry Protocol</strong>（OTLP）是一种开源的、与供应商无关的协议，用于从软件系统和应用程序收集、传输和导出遥测数据。</p><p>OTLP 定义了在检测应用程序和后端系统之间交换的连接格式和数据结构。它指定了遥测数据的编码格式，包括指标、跟踪和日志的模式，以及在网络中传输这些数据的规则。</p><p>OTLP 导出器允许将收集的遥测数据传输到后端进行处理和分析。</p></li></ul><h4 id="概览图"><a href="#概览图" class="headerlink" title="概览图"></a>概览图</h4><p>OpenTelemetry 架构旨在提供一种标准化的方法来收集、传输和处理来自应用程序和服务的遥测数据。它由几个关键组件组成，这些组件协同工作以实现分布式系统中的可观测性。</p><p><img src="https://file.chaofan.xyz/blog/202309300000200.png" alt="architecture"></p><h4 id="可观测性信号"><a href="#可观测性信号" class="headerlink" title="可观测性信号"></a>可观测性信号</h4><p>OpenTelemetry 提供了一种捕获可观测性信号的标准化方法：</p><ul><li><strong>Metrics</strong>(指标) 表明存在问题。</li><li><strong>Traces</strong>(跟踪) 会告诉你问题出在哪里。</li><li><strong>Logs</strong>(日志) 可以帮助您找到根本原因。</li></ul><p><a href="https://opentelemetry.io/docs/concepts/signals/metrics/">OpenTelemetry Metrics</a> 是帮助量化系统行为的定量指标。它们提供有关应用程序某些方面的当前状态或速率的信息，例如 CPU 使用情况、内存消耗或请求延迟。OpenTelemetry 允许您定义和记录自定义指标，以监视应用程序的性能和运行状况。</p><p><a href="https://opentelemetry.io/docs/concepts/signals/traces/">OpenTelemetry Traces</a> 提供了请求在分布式系统中执行路径的详细记录。它们捕获单个操作及其关系的时间信息，使您能够了解请求流、确定瓶颈并解决性能问题。使用 OpenTelemetry，您可以对代码进行检测，以生成分布式跟踪并在服务之间进行关联。</p><p><a href="https://opentelemetry.io/docs/concepts/signals/logs/">OpenTelemetry Logs</a> 是在应用程序执行期间发生的事件或消息的文本记录。它们帮助您理解应用程序行为、诊断问题和审计活动。OpenTelemetry 提供了一种机制，可以从应用程序中捕获结构化日志，并用上下文信息丰富它们。</p><h3 id="指标"><a href="#指标" class="headerlink" title="指标"></a>指标</h3><p>OpenTelemetry Metrics 是关于如何收集、聚合指标并将指标发送到<a href="https://uptrace.dev/get/opentelemetry-apm.html">OpenTelemetry APM 的标准</a>，如Prometheus 等工具。</p><h4 id="什么是指标"><a href="#什么是指标" class="headerlink" title="什么是指标"></a>什么是指标</h4><p>指标是代表系统运行状况和性能的数字数据点，例如 CPU 利用率、网络流量和数据库连接。</p><p>您可以使用指标来测量、监控和比较性能，例如，您可以测量服务器响应时间、内存利用率、错误率等。</p><h4 id="Prometheus"><a href="#Prometheus" class="headerlink" title="Prometheus"></a>Prometheus</h4><p>prometheus指标可以用OpenTelemetry Collector收集和暴露prometheus指标。可以配置 OpenTelemetry Collector 接受 OpenTelemetry 数据，然后使用Prometheus 远程写入协议保存到到 Prometheus。</p><p>或者执行相反的操作，使用 OpenTelemetry Collector 提取 Prometheus 指标并将其使用OTLP导出到OpenTelemetry 后端</p><p>OpenTelemetry Collector本身可以充当Prometheus Server来抓取数据，收集并处理，然后重新暴露出来（exporter）。</p><h5 id="OpenTelemetry-Collector-Prometheus-Exporter"><a href="#OpenTelemetry-Collector-Prometheus-Exporter" class="headerlink" title="OpenTelemetry Collector Prometheus Exporter"></a>OpenTelemetry Collector Prometheus Exporter</h5><p><a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/exporter/prometheusexporter">OpenTelemetry Collector Prometheus Exporter</a> 是一个组件，允许将指标从OpenTelemetry Collector 暴露到Prometheus监控系统，也可以添加额外的标签和元数据。</p><h4 id="从应用中暴露指标"><a href="#从应用中暴露指标" class="headerlink" title="从应用中暴露指标"></a>从应用中暴露指标</h4><p>通过使用OpenTelemetry <a href="https://opentelemetry.io/docs/concepts/instrumentation/">Instruments</a>，可以自动或者手动的将应用程序指标暴露出来。支持的类型有：</p><table><thead><tr><th>类型</th><th>属性</th><th>描述</th></tr></thead><tbody><tr><td>Counter</td><td><mark class="tag-plugin colorful mark" color="cyan">同步</mark> <mark class="tag-plugin colorful mark" color="green">单调</mark></td><td>用于描述加法非递减值</td></tr><tr><td>CounterObserver</td><td><mark class="tag-plugin colorful mark" color="cyan">异步</mark> <mark class="tag-plugin colorful mark" color="green">单调</mark></td><td>Counter的异步版本</td></tr><tr><td>UpDownCounter</td><td><mark class="tag-plugin colorful mark" color="cyan">同步</mark></td><td>可测量随时间增加或减少的附加值</td></tr><tr><td>UpDownCounterObserver</td><td><mark class="tag-plugin colorful mark" color="cyan">异步</mark></td><td>UpDownCounter的异步版本</td></tr><tr><td>Histogram</td><td><mark class="tag-plugin colorful mark" color="cyan">同步</mark></td><td>可根据记录的值生成直方图</td></tr><tr><td>GaugeObserver</td><td><mark class="tag-plugin colorful mark" color="cyan">异步</mark></td><td>用于测量非相加值</td></tr></tbody></table><p>具体用法参考各语言SDK实现，暴露出一个Prometheus指标端点即可。</p><h3 id="跟踪"><a href="#跟踪" class="headerlink" title="跟踪"></a>跟踪</h3><p><strong>跟踪</strong>为我们提供了向应用程序发出请求时发生的情况的总体情况。无论您的应用程序是具有单个数据库的整体应用程序还是复杂的服务网格，跟踪对于了解请求在应用程序中采用的完整“路径”都至关重要。</p><h4 id="分布式跟踪-1"><a href="#分布式跟踪-1" class="headerlink" title="分布式跟踪"></a>分布式跟踪</h4><p>分布式追踪允许您查看请求如何通过不同的服务和系统、每个操作的时间、发生时的任何日志和错误。</p><p>分布式追踪工具提供对系统行为的可见性，帮助识别性能问题，协助调试，并帮助确保分布式应用程序的可靠性和可伸缩性。</p><p>OpenTelemetry 跟踪在微服务架构的上下文中特别有价值，在微服务架构中，应用程序由多个独立的服务组成，共同工作以满足用户请求。</p><h4 id="Spans"><a href="#Spans" class="headerlink" title="Spans"></a>Spans</h4><p>span表示一个<strong>工作&#x2F;操作基本单元</strong>。一个Trace Tree由多个span组成。在OpenTelemetry中，span包含下面的信息：</p><ul><li>Name</li><li>Parent span ID (“空”表示 root spans)</li><li>Start and End Timestamps</li><li><a href="https://opentelemetry.io/docs/concepts/signals/traces/#span-context">Span Context</a></li><li><a href="https://opentelemetry.io/docs/concepts/signals/traces/#attributes">Attributes</a></li><li><a href="https://opentelemetry.io/docs/concepts/signals/traces/#span-events">Span Events</a></li><li><a href="https://opentelemetry.io/docs/concepts/signals/traces/#span-links">Span Links</a></li><li><a href="https://opentelemetry.io/docs/concepts/signals/traces/#span-status">Span Status</a></li></ul><p>比如：</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><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><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="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;trace_id&quot;</span><span class="punctuation">:</span> <span class="string">&quot;7bba9f33312b3dbb8b2c2c62bb7abe2d&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;parent_id&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;span_id&quot;</span><span class="punctuation">:</span> <span class="string">&quot;086e83747d0e381e&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;/v1/sys/health&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;start_time&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2022-10-22 16:04:01.209458162 +0000 UTC&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;end_time&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2022-10-22 16:04:01.209514132 +0000 UTC&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;status_code&quot;</span><span class="punctuation">:</span> <span class="string">&quot;STATUS_CODE_OK&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;status_message&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;attributes&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;net.transport&quot;</span><span class="punctuation">:</span> <span class="string">&quot;IP.TCP&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;net.peer.ip&quot;</span><span class="punctuation">:</span> <span class="string">&quot;172.17.0.1&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;net.peer.port&quot;</span><span class="punctuation">:</span> <span class="string">&quot;51820&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;net.host.ip&quot;</span><span class="punctuation">:</span> <span class="string">&quot;10.177.2.152&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;net.host.port&quot;</span><span class="punctuation">:</span> <span class="string">&quot;26040&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;http.method&quot;</span><span class="punctuation">:</span> <span class="string">&quot;GET&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;http.target&quot;</span><span class="punctuation">:</span> <span class="string">&quot;/v1/sys/health&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;http.server_name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;mortar-gateway&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;http.route&quot;</span><span class="punctuation">:</span> <span class="string">&quot;/v1/sys/health&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;http.user_agent&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Consul Health Check&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;http.scheme&quot;</span><span class="punctuation">:</span> <span class="string">&quot;http&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;http.host&quot;</span><span class="punctuation">:</span> <span class="string">&quot;10.177.2.152:26040&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;http.flavor&quot;</span><span class="punctuation">:</span> <span class="string">&quot;1.1&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;events&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;message&quot;</span><span class="punctuation">:</span> <span class="string">&quot;OK&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;timestamp&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2021-10-22 16:04:01.209512872 +0000 UTC&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>span允许嵌套，表示父子操作。</p><h5 id="上下文传播"><a href="#上下文传播" class="headerlink" title="上下文传播"></a>上下文传播</h5><p>上下文传播可确保相关上下文数据（例如跟踪 ID、跨度 ID 和其他元数据）在应用程序的不同服务和组件之间一致地传播。</p><p>OpenTemetry 在进程内的函数之间传播上下文（进程内传播），甚至从一个服务传播到另一个服务（分布式传播）。</p><p>进程内传播可以是隐式的或显式的，具体取决于您使用的编程语言。隐式传播是通过将活动上下文存储在线程局部变量（Java、Python、Ruby、NodeJS）中自动完成的。显式传播需要将活动上下文作为参数从一个函数显式传递到另一个函数 (Go)。</p><p>对于分布式上下文传播，OpenTelemetry 支持多种定义如何序列化和传递上下文数据的协议：</p><ul><li><a href="https://www.w3.org/TR/trace-context/">W3C 跟踪上下文</a>在<code>traceparent</code>头中，例如<code>traceparent=00-84b54e9330faae5350f0dd8673c98146-279fa73bc935cc05-01</code>.</li><li><a href="https://github.com/openzipkin/b3-propagation">B3 Zipkin</a>以 开头的标头<code>x-b3-</code>，例如<code>X-B3-TraceId</code>.</li></ul><p>W3C 跟踪上下文是默认启用的推荐传播器。</p><h3 id="日志"><a href="#日志" class="headerlink" title="日志"></a>日志</h3><p>OpenTelemetry Logs 允许以能够与其他可观测信号进行关联和更好集成的方式记录和收集日志。</p><p>日志对于了解应用程序行为、诊断问题和监控系统运行状况至关重要。虽然分布式跟踪和指标可以提供有关系统性能的相关信息，但日志可以提供有关特定事件、错误和应用程序行为的详细上下文和信息。</p><p>OpenTelemetry 提供了一个日志记录 API，允许您检测应用程序并生成<strong>结构化日志</strong>。OpenTelemetry Logging API 旨在与其他遥测数据（例如指标和跟踪）配合使用，以提供统一的可观测性解决方案。</p><h4 id="不同类型的日志"><a href="#不同类型的日志" class="headerlink" title="不同类型的日志"></a>不同类型的日志</h4><p>OpenTelemetry 支持从应用程序或系统内的各种来源捕获日志。根据日志的生成和收集方式，日志可以分为 3 类。</p><h5 id="系统和基础设施日志"><a href="#系统和基础设施日志" class="headerlink" title="系统和基础设施日志"></a>系统和基础设施日志</h5><p>系统日志提供有关系统操作、性能和安全性的宝贵信息。系统日志通常由系统内的各个组件生成，包括操作系统、应用程序、网络设备和服务器。</p><p>系统日志是在主机级别写入的，具有预定义的格式和内容，无法轻易更改。系统日志不包含有关跟踪上下文的信息。</p><p>比如内核日志，系统服务日志。</p><h5 id="已存在应用日志"><a href="#已存在应用日志" class="headerlink" title="已存在应用日志"></a>已存在应用日志</h5><p>第一方日志由内部应用程序生成，记录特定的应用程序事件、错误和用户活动。这些日志对于应用程序调试和故障排除很有用。</p><p>通常，开发人员可以修改这些应用程序以更改日志的写入方式以及包含的信息。例如，要将日志与跟踪关联起来，开发人员可以手动将跟踪上下文添加到每个日志语句中，或者使用日志库的插件自动执行此操作。</p><p>例如，要传播上下文并将日志记录与跨度关联，您可以在日志消息中使用以下属性：</p><ul><li><code>trace_id</code>对于 TraceId，十六进制编码。</li><li><code>span_id</code>对于 SpanId，十六进制编码。</li><li><code>trace_flags</code>对于跟踪标志，根据 W3C 跟踪标志格式进行格式化。</li></ul><p>例如：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">request failed trace_id=958180131ddde684c1dbda1aeacf51d3 span_id=0cf859e4f7510204</span><br></pre></td></tr></table></figure><h5 id="新项目日志"><a href="#新项目日志" class="headerlink" title="新项目日志"></a>新项目日志</h5><p>开始新项目时，您可以遵循 OpenTelemetry 的建议和最佳实践，了解如何使用自动检测或配置日志记录库以使用 OpenTelemetry 日志附加程序来发出日志。</p><p>OpenTelemetry 的日志 API 允许开发人员对其应用程序进行检测，以生成可以由日志记录后端或日志管理系统收集和处理的结构化日志。日志 API 提供了一种将附加上下文信息附加到日志的方法，例如标签、属性或元数据。</p><p>使用 Logger API 记录不同严重级别的事件或消息，例如<code>debug</code>、<code>info</code>、<code>warning</code>、<code>error</code>等。您还可以将其他属性或上下文附加到日志以提供更多信息。</p><p>OpenTelemetry 还提供了一种标准化方法来跨分布式系统传播日志中的上下文。这确保了一致地捕获和保留相关的执行上下文，即使日志是由系统的不同组件生成的。</p><p>OpenTelemetry 日志<a href="https://opentelemetry.io/docs/specs/otel/logs/data-model/#field-traceid">数据模型</a>允许将<code>TraceId</code>和<code>SpanId</code>直接包含在<code>LogRecords</code>.</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>通过OpenTelemetry，我们可以在分布式环境中，跨语言，对服务的可遥测数据（Traces， Metrics， Logs）进行标准化的，统一的定义，埋点，收集，处理和存储，降低实施可观测性的成本。</p>]]>
    </content>
    <id>https://chaofan.xyz/posts/29a4bd97/</id>
    <link href="https://chaofan.xyz/posts/29a4bd97/"/>
    <published>2023-09-28T00:07:29.000Z</published>
    <summary>
      <![CDATA[<p><a href="https://opentelemetry.io/">OpenTelemetry</a> 是一个开源的可观测性框架，由云原生基金会(<a href="https://www.cncf.io/">CNCF</a>)托管。它是 <a href="https://opencensus.io/">OpenCensus</a> 和 <a href="https://opentracing.io/">OpenTracing</a> 项目的合并。旨在为所有类型的可观测信号(如<strong>跟踪（Traces）</strong>、<strong>指标（Metrics）<strong>和</strong>日志（Logs）</strong>)提供单一标准。</p>]]>
    </summary>
    <title>云原生可观测性之OpenTelemetry</title>
    <updated>2026-08-09T09:24:32.640Z</updated>
  </entry>
  <entry>
    <author>
      <name>chaofan</name>
      <email>chaofanxy@outlook.com</email>
    </author>
    <category term="Linux" scheme="https://chaofan.xyz/categories/Linux/"/>
    <category term="systemd" scheme="https://chaofan.xyz/tags/systemd/"/>
    <content>
      <![CDATA[<p>Systemd 是 Linux 系统工具，用来启动守护进程，已成为大多数发行版的标准配置。</p><span id="more"></span><h2 id="由来"><a href="#由来" class="headerlink" title="由来"></a>由来</h2><p>历史上，Linux 的启动一直采用 init 进程。</p><p>下面的命令用来启动服务。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> /etc/init.d/apache2 start</span><br><span class="line"><span class="comment"># 或者</span></span><br><span class="line">service apache2 start</span><br></pre></td></tr></table></figure><p>这种方法有两个缺点。</p><p>一是启动时间长。init 进程是串行启动，只有前一个进程启动完，才会启动下一个进程。</p><p>二是启动脚本复杂。init 进程只是执行启动脚本，不管其他事情。脚本需要自己处理各种情况，这往往使得脚本变得很长。</p><p>Systemd 就是为了解决这些问题而诞生的。</p><p>它的设计目标是</p><blockquote><p>为系统的启动和管理提供一套完整的解决方案</p></blockquote><h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>Systemd 是一系列工具的集合，其作用也远远不仅是启动操作系统，它还接管了后台服务、结束、状态查询，以及日志归档、设备管理、电源管理、定时任务等许多职责，并支持通过特定事件（如插入特定 USB 设备）和特定端口数据触发的 On-demand（按需）任务。</p><p>Systemd 的后台服务还有一个特殊的身份——它是系统中 PID 值为 1 的进程。</p><p>更少的进程<br>Systemd 提供了 服务按需启动 的能力，使得特定的服务只有在真定被请求时才启动。</p><p>允许更多的进程并行启动<br>在 SysV-init 时代，将每个服务项目编号依次执行启动脚本。Ubuntu 的 Upstart 解决了没有直接依赖的启动之间的并行启动。而 Systemd 通过 Socket 缓存、DBus 缓存和建立临时挂载点等方法进一步解决了启动进程之间的依赖，做到了所有系统服务并发启动。对于用户自定义的服务，Systemd 允许配置其启动依赖项目，从而确保服务按必要的顺序运行。</p><p>使用 CGroup 跟踪和管理进程的生命周期<br>在 Systemd 之间的主流应用管理服务都是使用 进程树 来跟踪应用的继承关系的，而进程的父子关系很容易通过 两次 fork 的方法脱离。</p><p>而 Systemd 则提供通过 CGroup 跟踪进程关系，引补了这个缺漏。通过 CGroup 不仅能够实现服务之间访问隔离，限制特定应用程序对系统资源的访问配额，还能更精确地管理服务的生命周期。</p><p>统一管理服务日志<br>Systemd 是一系列工具的集合， 包括了一个专用的系统日志管理服务：Journald。这个服务的设计初衷是克服现有 Syslog 服务的日志内容易伪造和日志格式不统一等缺点，Journald 用 二进制格式 保存所有的日志信息，因而日志内容很难被手工伪造。Journald 还提供了一个 journalctl 命令来查看日志信息，这样就使得不同服务输出的日志具有相同的排版格式， 便于数据的二次处理。</p><h2 id="资源类型"><a href="#资源类型" class="headerlink" title="资源类型"></a>资源类型</h2><p>Systemd 可以管理所有系统资源，不同的资源统称为 Unit（单位）。</p><p>在 Systemd 的生态圈中，Unit 文件统一了过去各种不同系统资源配置格式，例如服务的启&#x2F;停、定时任务、设备自动挂载、网络配置、虚拟内存配置等。而 Systemd 通过不同的文件后缀来区分这些配置文件。</p><p>Systemd 支持的 12 种 Unit 文件类型</p><ul><li>.automount：用于控制自动挂载文件系统，相当于 SysV-init 的 autofs 服务</li><li>.device：对于 &#x2F;dev 目录下的设备，主要用于定义设备之间的依赖关系</li><li>.mount：定义系统结构层次中的一个挂载点，可以替代过去的 &#x2F;etc&#x2F;fstab 配置文件</li><li>.path：用于监控指定目录或文件的变化，并触发其它 Unit 运行</li><li>.scope：这种 Unit 文件不是用户创建的，而是 Systemd 运行时产生的，描述一些系统服务的分组信息</li><li>.service：封装守护进程的启动、停止、重启和重载操作，是最常见的一种 Unit 文件</li><li>.slice：用于表示一个 CGroup 的树，通常用户不会自己创建这样的 Unit 文件</li><li>.snapshot：用于表示一个由 systemctl snapshot 命令创建的 Systemd Units 运行状态快照</li><li>.socket：监控来自于系统或网络的数据消息，用于实现基于数据自动触发服务启动</li><li>.swap：定义一个用户做虚拟内存的交换分区</li><li>.target：用于对 Unit 文件进行逻辑分组，引导其它 Unit 的执行。它替代了 SysV-init 运行级别的作用，并提供更灵活的基于特定设备事件的启动方式</li><li>.timer：用于配置在特定时间触发的任务，替代了 Crontab 的功能</li></ul><h3 id="目录"><a href="#目录" class="headerlink" title="目录"></a>目录</h3><p>按照 systemd 的约定，unit 文件应该方案指定的三个位置之一。</p><ul><li>&#x2F;etc&#x2F;systemd&#x2F;system：系统或用户自定义的配置文件</li><li>&#x2F;run&#x2F;systemd&#x2F;system：软件运行时生成的配置文件</li><li>&#x2F;usr&#x2F;lib&#x2F;systemd&#x2F;system：系统或第三方软件安装时添加的配置文件。</li></ul><p>这三个目录具有优先级，越靠上的优先级越高</p><h2 id="Service-Unit"><a href="#Service-Unit" class="headerlink" title="Service Unit"></a>Service Unit</h2><p>文件结构</p><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><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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="section">[Unit]</span></span><br><span class="line"><span class="attr">Description</span>=Hello World</span><br><span class="line"><span class="attr">After</span>=docker.service</span><br><span class="line"><span class="attr">Requires</span>=docker.service</span><br><span class="line"><span class="section">[Service]</span></span><br><span class="line"><span class="attr">TimeoutStartSec</span>=<span class="number">0</span></span><br><span class="line"><span class="attr">ExecStartPre</span>=-/usr/bin/docker kill busybox1</span><br><span class="line"><span class="attr">ExecStartPre</span>=-/usr/bin/docker rm busybox1</span><br><span class="line"><span class="attr">ExecStartPre</span>=/usr/bin/docker pull busybox</span><br><span class="line"><span class="attr">ExecStart</span>=/usr/bin/docker run --name busybox1 busybox /bin/ sh -c <span class="string">&quot;while true; do echo Hello World; sleep 1; done&quot;</span></span><br><span class="line"><span class="attr">ExecStop</span>=<span class="string">&quot;/usr/bin/docker stop busybox1&quot;</span></span><br><span class="line"><span class="attr">ExecStopPost</span>=<span class="string">&quot;/usr/bin/docker rm busybox1&quot;</span></span><br><span class="line"><span class="section">[Install]</span></span><br><span class="line"><span class="attr">WantedBy</span>=multi-user.target</span><br></pre></td></tr></table></figure><p>如下所示，Systemd 服务的 Unit 文件可以分为三个配置区段：</p><ul><li>Unit 和 Install 段：所有 Unit 文件通用，用于配置服务（或其它系统资源）的描述、依赖和随系统启动的方式</li><li>Service 段：服务（Service）类型的 Unit 文件（后缀为 .service）特有的，用于定义服务的具体管理和操作方法</li></ul><h3 id="Unit-段"><a href="#Unit-段" class="headerlink" title="Unit 段"></a>Unit 段</h3><ul><li>Description：描述这个 Unit 文件的信息</li><li>Documentation：指定服务的文档，可以是一个或多个文档的 URL 路径</li><li>Requires：依赖的其它 Unit 列表，列在其中的 Unit 模板会在这个服务启动时的同时被启动。并且，如果其中任意一个服务启动失败，这个服务也会被终止</li><li>Wants：与 Requires 相似，但只是在被配置的这个 Unit 启动时，触发启动列出的每个 Unit 模块，而不去考虑这些模板启动是否成功</li><li>After：与 Requires 相似，但是在后面列出的所有模块全部启动完成以后，才会启动当前的服务</li><li>Before：与 After 相反，在启动指定的任务一个模块之间，都会首先确证当前服务已经运行</li><li>Binds To：与 Requires 相似，失败时失败，成功时成功，但是在这些模板中有任意一个出现意外结束或重启时，这个服务也会跟着终止或重启</li><li>Part Of：一个 Bind To 作用的子集，仅在列出的任务模块失败或重启时，终止或重启当前服务，而不会随列出模板的启动而启动</li><li>OnFailure：当这个模板启动失败时，就会自动启动列出的每个模块</li><li>Conflicts：与这个模块有冲突的模块，如果列出的模块中有已经在运行的，这个服务就不能启动，反之亦然</li></ul><h3 id="Install-段"><a href="#Install-段" class="headerlink" title="Install 段"></a>Install 段</h3><p>这部分配置的目标模块通常是特定运行目标的 .target 文件，用来使得服务在系统启动时自动运行。这个区段可以包含三种启动约束：</p><ul><li>WantedBy：和 Unit 段的 Wants 作用相似，只有后面列出的不是服务所依赖的模块，而是依赖当前服务的模块。它的值是一个或多个 Target，当前 Unit 激活时（enable）符号链接会放入 &#x2F;etc&#x2F;systemd&#x2F;system 目录下面以 &lt;\Target 名&gt; + .wants 后缀构成的子目录中，如 “&#x2F;etc&#x2F;systemd&#x2F;system&#x2F;multi-user.target.wants&#x2F;“</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><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"># find /etc/systemd/system/* -type d</span></span><br><span class="line">/etc/systemd/system/default.target.wants</span><br><span class="line">/etc/systemd/system/getty.target.wants</span><br><span class="line">/etc/systemd/system/graphical.target.wants</span><br><span class="line">/etc/systemd/system/multi-user.target.wants</span><br><span class="line">/etc/systemd/system/network-online.target.wants</span><br><span class="line">/etc/systemd/system/paths.target.wants</span><br><span class="line">/etc/systemd/system/shutdown.target.wants</span><br><span class="line">/etc/systemd/system/sockets.target.wants</span><br><span class="line">/etc/systemd/system/sysinit.target.wants</span><br><span class="line">/etc/systemd/system/timers.target.wants</span><br></pre></td></tr></table></figure><ul><li>RequiredBy：和 Unit 段的 Wants 作用相似，只有后面列出的不是服务所依赖的模块，而是依赖当前服务的模块。它的值是一个或多个 Target，当前 Unit 激活时，符号链接会放入 &#x2F;etc&#x2F;systemd&#x2F;system 目录下面以 &lt;\Target 名&gt; + .required 后缀构成的子目录中</li><li>Also：当前 Unit enable&#x2F;disable 时，同时 enable&#x2F;disable 的其他 Unit</li><li>Alias：当前 Unit 可用于启动的别名</li></ul><h3 id="Service-段"><a href="#Service-段" class="headerlink" title="Service 段"></a>Service 段</h3><p>用来 Service 的配置，只有 Service 类型的 Unit 才有这个区块。它的主要字段分为服务生命周期和服务上下文配置两个方面。</p><p>服务生命周期控制相关</p><ul><li><p>Type：定义启动时的进程行为，它有以下几种值：</p><ul><li><p>Type&#x3D;simple：默认值，执行 ExecStart 指定的命令，启动主进程</p></li><li><p>Type&#x3D;forking：以 fork 方式从父进程创建子进程，创建后父进程会立即退出</p></li><li><p>Type&#x3D;oneshot：一次性进程，Systemd 会等当前服务退出，再继续往下执行</p></li><li><p>Type&#x3D;dbus：当前服务通过 D-Bus 启动</p></li><li><p>Type&#x3D;notify：当前服务启动完毕，会通知 Systemd，再继续往下执行</p></li></ul></li><li><p>Type&#x3D;idle：若有其他任务执行完毕，当前服务才会运行</p></li><li><p>RemainAfterExit：值为 true 或 false（默认）。当配置为 true 时，Systemd 只会负责启动服务进程，之后即便服务进程退出了，Systemd 也仍然会认为这个服务还在运行中。这个配置主要是提供给一些并非常驻内存，而是启动注册后立即退出，然后等待消息按需启动的特殊类型服务使用的。</p></li><li><p>ExecStart：启动当前服务的命令</p></li><li><p>ExecStartPre：启动当前服务之前执行的命令</p></li><li><p>ExecStartPos：启动当前服务之后执行的命令</p></li><li><p>ExecReload：重启当前服务时执行的命令</p></li><li><p>ExecStop：停止当前服务时执行的命令</p></li><li><p>ExecStopPost：停止当其服务之后执行的命令</p></li><li><p>RestartSec：自动重启当前服务间隔的秒数</p></li><li><p>Restart：定义何种情况 Systemd 会自动重启当前服务，可能的值包括 always（总是重启）、on-success、on-failure、on-abnormal、on-abort、on-watchdog</p></li><li><p>TimeoutStartSec：启动服务时等待的秒数，这一配置对于使用 Docker 容器而言显得尤为重要，因其第一次运行时可能需要下载镜像，严重延时会容易被 Systemd 误判为启动失败杀死。通常，对于这种服务，将此值指定为 0，从而关闭超时检测</p></li><li><p>TimeoutStopSec：停止服务时的等待秒数，如果超过这个时间仍然没有停止，Systemd 会使用 SIGKILL 信号强行杀死服务的进程</p></li><li><p>服务上下文配置相关</p></li><li><p>Environment：为服务指定环境变量</p></li><li><p>EnvironmentFile：指定加载一个包含服务所需的环境变量的列表的文件，文件中的每一行都是一个环境变量的定义</p></li><li><p>Nice：服务的进程优先级，值越小优先级越高，默认为 0。其中 -20 为最高优先级，19 为最低优先级</p></li><li><p>WorkingDirectory：指定服务的工作目录</p></li><li><p>RootDirectory：指定服务进程的根目录（&#x2F; 目录）。如果配置了这个参数，服务将无法访问指定目录以外的任何文件</p></li><li><p>User：指定运行服务的用户</p></li><li><p>Group：指定运行服务的用户组</p></li><li><p>MountFlags：服务的 Mount Namespace 配置，会影响进程上下文中挂载点的信息，即服务是否会继承主机上已有挂载点，以及如果服务运行执行了挂载或卸载设备的操作，是否会真实地在主机上产生效果。可选值为 shared、slaved 或 private</p></li><li><p>shared：服务与主机共用一个 Mount Namespace，继承主机挂载点，且服务挂载或卸载设备会真实地反映到主机上</p></li><li><p>slave：服务使用独立的 Mount Namespace，它会继承主机挂载点，但服务对挂载点的操作只有在自己的 Namespace 内生效，不会反映到主机上</p></li><li><p>private：服务使用独立的 Mount Namespace，它在启动时没有任何任何挂载点，服务对挂载点的操作也不会反映到主机上</p></li><li><p>LimitCPU &#x2F; LimitSTACK &#x2F; LimitNOFILE &#x2F; LimitNPROC 等：限制特定服务的系统资源量，例如 CPU、程序堆栈、文件句柄数量、子进程数量等</p></li></ul><h2 id="Unit-文件占位符和模板"><a href="#Unit-文件占位符和模板" class="headerlink" title="Unit 文件占位符和模板"></a>Unit 文件占位符和模板</h2><h3 id="Unit-文件占位符"><a href="#Unit-文件占位符" class="headerlink" title="Unit 文件占位符"></a>Unit 文件占位符</h3><p>在 Unit 文件中，有时会需要使用到一些与运行环境有关的信息，例如节点 ID、运行服务的用户等。这些信息可以使用占位符来表示，然后在实际运行被动态地替换实际的值。</p><ul><li>%n：完整的 Unit 文件名字，包括 .service 后缀名</li><li>%p：Unit 模板文件名中 @ 符号之前的部分，不包括 @ 符号</li><li>%i：Unit 模板文件名中 @ 符号之后的部分，不包括 @ 符号和 .service 后缀名</li><li>%t：存放系统运行文件的目录，通常是 “run”</li><li>%u：运行服务的用户，如果 Unit 文件中没有指定，则默认为 root</li><li>%U：运行服务的用户 ID</li><li>%h：运行服务的用户 Home 目录，即 %{HOME} 环境变量的值</li><li>%s：运行服务的用户默认 Shell 类型，即 %{SHELL} 环境变量的值</li><li>%m：实际运行节点的 Machine ID，对于运行位置每个的服务比较有用</li><li>%b：Boot ID，这是一个随机数，每个节点各不相同，并且每次节点重启时都会改变</li><li>%H：实际运行节点的主机名</li><li>%v：内核版本，即 “uname -r” 命令输出的内容</li><li>%%：在 Unit 模板文件中表示一个普通的百分号</li></ul>]]>
    </content>
    <id>https://chaofan.xyz/posts/c670b61f/</id>
    <link href="https://chaofan.xyz/posts/c670b61f/"/>
    <published>2023-04-11T18:27:05.000Z</published>
    <summary>
      <![CDATA[<p>Systemd 是 Linux 系统工具，用来启动守护进程，已成为大多数发行版的标准配置。</p>]]>
    </summary>
    <title>使用systemd管理服务</title>
    <updated>2026-08-09T09:24:32.640Z</updated>
  </entry>
  <entry>
    <author>
      <name>chaofan</name>
      <email>chaofanxy@outlook.com</email>
    </author>
    <category term="Linux" scheme="https://chaofan.xyz/categories/Linux/"/>
    <category term="zsh" scheme="https://chaofan.xyz/tags/zsh/"/>
    <category term="bash" scheme="https://chaofan.xyz/tags/bash/"/>
    <category term="shell" scheme="https://chaofan.xyz/tags/shell/"/>
    <content>
      <![CDATA[<p>Zsh（Z-shell）是一款用于交互式使用的shell，也可以作为脚本解释器来使用。其包含了 bash，ksh，tcsh 等其他shell中许多优秀功能，也拥有诸多自身特色。</p><span id="more"></span><p>Shell是Linux&#x2F;Unix的一个外壳，你理解成衣服也行。它负责外界与Linux内核的交互，接收用户或其他应用程序的命令，然后把这些命令转化成内核能理解的语言，传给内核，内核是真正干活的，干完之后再把结果返回用户或应用程序。</p><p>Linux&#x2F;Unix提供了很多种Shell，为毛要这么多Shell？难道用来炒着吃么？那我问你，你同类型的衣服怎么有那么多件？花色，质地还不一样。写程序比买衣服复杂多了，而且程序员往往负责把复杂的事情搞简单，简单的事情搞复杂。牛程序员看到不爽的Shell，就会自己重新写一套，慢慢形成了一些标准，常用的Shell有这么几种，sh、bash、csh等，想知道你的系统有几种shell，可以通过以下命令查看：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cat /etc/shells</span><br></pre></td></tr></table></figure><p>目前常用的 Linux 系统的默认 Shell 都是 bash，但是真正强大的 Shell 是深藏不露的 zsh， 绝对是马车中的跑车，跑车中的<em>战斗机</em>，史称『终极 Shell』，但是由于配置过于复杂，所以初期无人问津，很多人跑过来看看 zsh 的配置指南，什么都不说转身就走了。直到有一天，国外有个穷极无聊的程序员开发出了一个能够让你快速上手的zsh项目，叫做「<a href="https://github.com/ohmyzsh/ohmyzsh">oh my zsh</a>」。这玩意就像「X天叫你学会 C++」系列，可以让你神功速成。</p><p>好，下面我们看看如何安装、配置和使用 zsh。</p><h1 id="安装zsh"><a href="#安装zsh" class="headerlink" title="安装zsh"></a>安装zsh</h1><p>如果你用 Mac，就可以直接看下一节，<em>默认就是zsh</em><br>如果你用 Ubuntu Linux，执行：</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">sudo apt install zsh</span><br></pre></td></tr></table></figure><p>如果你用 Windows……去洗洗睡吧。</p><p>安装完成后设置当前用户使用 zsh：</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">chsh -s /bin/zsh</span><br></pre></td></tr></table></figure><p>根据提示输入当前用户的密码就可以了。</p><h1 id="安装oh-my-zsh"><a href="#安装oh-my-zsh" class="headerlink" title="安装oh my zsh"></a>安装oh my zsh</h1><p>首先安装 git，安装方式同上。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo apt install git</span><br></pre></td></tr></table></figure><p>安装「oh my zsh」可以自动安装也可以手动安装。</p><p>自动安装：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">wget https://github.com/robbyrussell/oh-my-zsh/raw/master/tools/install.sh -O - | sh</span><br></pre></td></tr></table></figure><p>手动安装：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git clone git://github.com/robbyrussell/oh-my-zsh.git ~/.oh-my-zsh</span><br><span class="line">cp ~/.oh-my-zsh/templates/zshrc.zsh-template ~/.zshrc</span><br></pre></td></tr></table></figure><p>都不复杂，安装完成之后退出当前会话重新打开一个终端窗口，看到终端提示符变化了，就代表安装完成了：</p><h1 id="配置"><a href="#配置" class="headerlink" title="配置"></a>配置</h1><p>zsh 的配置主要集中在用户当前目录的.zshrc里，用 vim 或你喜欢的其他编辑器打开.zshrc，在最下面会发现这么一行字：</p><p># Customize to your needs…</p><p>可以在此处定义自己的环境变量和别名，当然，oh my zsh 在安装时已经自动读取当前的环境变量并进行了设置</p><p>zsh 的厉害之处在于不仅可以设置通用别名，还能针对文件类型设置对应的打开程序，比如：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">alias -s html=gedit</span><br></pre></td></tr></table></figure><p>意思就是你在命令行输入 hello.html，zsh会为你自动打开 gedit 并读取 hello.html</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">alias -s gz=&#x27;tar -xzvf</span><br></pre></td></tr></table></figure><p>表示自动解压后缀为 gz 的压缩包。</p><p>总之，只有想不到，没有做不到。</p><p>设置完环境变量和别名之后，基本上就可以用了，如果你是个主题控，还可以玩玩 zsh 的主题。在 .zshrc 里找到ZSH_THEME，就可以设置主题了，默认主题是：</p><p>ZSH_THEME&#x3D;”robbyrussell”</p><p>oh my zsh 提供了数十种主题，相关文件在~&#x2F;.oh-my-zsh&#x2F;themes目录下，你可以随意选择，也可以编辑主题满足自己的<em>变态需求</em></p><h1 id="插件"><a href="#插件" class="headerlink" title="插件"></a>插件</h1><p>oh my zsh 项目提供了完善的插件体系，相关的文件在~&#x2F;.oh-my-zsh&#x2F;plugins目录下，默认提供了100多种，可以根据自己的实际工作环境采用，想了解每个插件的功能，只要打开相关目录下的 zsh 文件看一下就知道了。插件也是在.zshrc里配置，找到plugins关键字，你就可以加载自己的插件了，系统默认加载 git ，你可以在后面追加内容，如下：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">plugins=(git autojump mvn gradle)</span><br></pre></td></tr></table></figure><h1 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h1><p>1、兼容 bash，原来使用 bash 的兄弟切换过来毫无压力，该咋用咋用。</p><p>2、强大的历史纪录功能，输入 grep 然后用上下箭头可以翻阅你执行的所有 grep 命令。</p><p>3、智能拼写纠正，输入gtep mactalk * -R，系统会提示：zsh: correct ‘gtep’ to ‘grep’ [nyae]? 比妹纸贴心吧，她们向来都是让你猜的……</p><p>4、各种补全：路径补全、命令补全，命令参数补全，插件内容补全等等。触发补全只需要按一下或两下 tab 键，补全项可以使用 ctrl+n&#x2F;p&#x2F;f&#x2F;b上下左右切换。比如你想杀掉 java 的进程，只需要输入 kill java + tab键，如果只有一个 java 进程，zsh 会自动替换为进程的 pid，如果有多个则会出现选择项供你选择。ssh + 空格 + 两个tab键，zsh会列出所有访问过的主机和用户名进行补全</p><p>5、目录浏览和跳转：输入 d，即可列出你在这个会话里访问的目录列表，输入列表前的序号，即可直接跳转。</p><p>6、在当前目录下输入 .. 或 … ，或直接输入当前目录名都可以跳转，你甚至不再需要输入 cd 命令了。</p><p>7、通配符搜索：ls -l **&#x2F;*.sh，可以递归显示当前目录下的 shell 文件，文件少时可以代替 find，文件太多就歇菜了。</p><p>8、更强的别名</p><p>9、插件支持</p><p>……</p><p>看完这篇文章，你就知道，zsh一出，无人再与争锋！终极二字不是盖的。</p><p>如果你是个正在使用 shell程序员，如果你依然准备使用 bash，那就去面壁和忏悔吧，别说你是程序员…</p><p>感谢那位开发了 oh my zsh 的无聊程序员，他可能没有因此收获物质上的利益，但是他的代码提升了无数程序员的效率，节省了大量的时间，我们说，程序员改变世界！</p><h1 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h1><p><a href="https://zhuanlan.zhihu.com/p/61447507">zsh常用插件</a></p><p><a href="https://github.com/ohmyzsh/ohmyzsh/wiki/Themes">zsh官方主题站</a></p>]]>
    </content>
    <id>https://chaofan.xyz/posts/fb612641/</id>
    <link href="https://chaofan.xyz/posts/fb612641/"/>
    <published>2023-01-14T14:39:47.000Z</published>
    <summary>
      <![CDATA[<p>Zsh（Z-shell）是一款用于交互式使用的shell，也可以作为脚本解释器来使用。其包含了 bash，ksh，tcsh 等其他shell中许多优秀功能，也拥有诸多自身特色。</p>]]>
    </summary>
    <title>zsh-终极shell</title>
    <updated>2026-08-09T09:24:32.640Z</updated>
  </entry>
  <entry>
    <author>
      <name>chaofan</name>
      <email>chaofanxy@outlook.com</email>
    </author>
    <category term="Linux" scheme="https://chaofan.xyz/categories/Linux/"/>
    <category term="Linux" scheme="https://chaofan.xyz/tags/Linux/"/>
    <category term="Archlinux" scheme="https://chaofan.xyz/tags/Archlinux/"/>
    <content>
      <![CDATA[<p>因为前两天win11不知道怎么了把我的账户设置成了来宾账户，导致几乎所有软件都无法使用，遂决定格盘重装，想回到 Arch 好久了，借此机会重新体验一次。</p><span id="more"></span><p>在这之前，希望你至少按照<a href="https://wiki.archlinux.org/title/Installation_guide">ArchWiki</a>中的安装向导，在安装过程中遇到问题之后再来参考本文。</p><h1 id="制作引导"><a href="#制作引导" class="headerlink" title="制作引导"></a>制作引导</h1><h2 id="下载镜像"><a href="#下载镜像" class="headerlink" title="下载镜像"></a>下载镜像</h2><p>最推荐的方式还是去<a href="https://archlinux.org/download/">官网</a>下载，网站下方除了提供种子外还有各个国家的镜像站，选择一个离自己物理位置近的下载即可。</p><p>下载完成就是一个大约 800M 大小的 iso 镜像，可以看到，相对于动辄好几G的其他常见的发行版，archlinux 可以说是非常的轻量，基本上允许你从头一步一步安装一个属于你自己的 archlinux !</p><h2 id="制作引导盘"><a href="#制作引导盘" class="headerlink" title="制作引导盘"></a>制作引导盘</h2><p>我一般会选择图形化页面的软件方便烧录，这里推荐 <a href="https://www.balena.io/etcher/">Etcher</a> ， 提供了Windows, MacOS, Linux所有平台的安装包。</p><p><img src="https://file.chaofan.xyz/blog/image-20230114171052491.png"></p><p>按照如图所示的顺序很方便的就能烧录好U盘，最后重启电脑进入U盘启动，就能愉快的开始安装啦😃</p><h1 id="开始安装"><a href="#开始安装" class="headerlink" title="开始安装"></a>开始安装</h1><h2 id="远程安装"><a href="#远程安装" class="headerlink" title="远程安装"></a>远程安装</h2><p>安装过程中会输入大量的命令，如果害怕敲错，可以用另一台设备 ssh 连接过来远程操作安装，方便复制命令，操作步骤如下：</p><ol><li>设置 root 密码</li></ol><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">passwd</span><br></pre></td></tr></table></figure><ol start="2"><li>查看当前局域网ip地址</li></ol><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ip addr</span><br></pre></td></tr></table></figure><p>找到当前网卡的ip地址即可，一般可能是 <code>172.xxx.xxx.xxx</code> 或者 <code>192.168.xxx.xxx</code> ，并记下来。</p><ol start="3"><li>通过 ssh 连接即可</li></ol><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ssh root@172.xxx.xxx.xxx</span><br></pre></td></tr></table></figure><p>输入密码后就能连接上了。之后安装本地安装的顺序继续走就可以。</p><h2 id="本地安装"><a href="#本地安装" class="headerlink" title="本地安装"></a>本地安装</h2><p>如果是第一次安装或者抱着学习的态度，还是建议本地安装，亲自敲一遍命令，你可以清楚的知道一个 linux 设备从零到能用中间基本经历了什么操作。</p><blockquote><p>这大概也是 Archlinux 的魅力之一吧，常见的 Ubuntu 等安装时都提供了图形界面安装，只需要一直点击下一步就能成功安装，小白可能安装会快点，但是作为计算机从业人员，如果如果连 Linux 基本的系统结构都不清楚的话，还是不太合适的。</p></blockquote><h3 id="联网"><a href="#联网" class="headerlink" title="联网"></a>联网</h3><p>如果是网线连接，一般会自动连接上，可以跳过这部分。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">nmcli dev wifi connect ChinaNet-sj8h password 12345678</span><br></pre></td></tr></table></figure><p>其中，<code>ChinaNet-sj8h</code> 部分是 WiFi 的名字，后面的 <code>12345678</code> 是你的 WiFi 密码。</p><details class="tag-plugin colorful folding" color="yellow" child="codeblock"><summary><p>网卡被禁用？</p></summary><div class="body"><p>有时候会遇到连接 WiFi 的时候提示网卡被禁用，可以通过下面的操作恢复：</p> <figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">rfkill    # 查看被禁用的设备</span><br><span class="line">rfkill unblock 1    # 启用设备</span><br></pre></td></tr></table></figure></div></details><p>连接完成后，执行 <code>ping baidu.com</code> 可以检查网络是否正常。<del>baidu 每天承受大量ddos攻击</del> <span class="tag-plugin emoji"><img no-lazy="" class="inline" src="https://gcore.jsdelivr.net/gh/cdn-x/emoji/tieba/$滑稽.png"/></span></p><h3 id="同步硬件时间"><a href="#同步硬件时间" class="headerlink" title="同步硬件时间"></a>同步硬件时间</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">timedatectl set-ntp true</span><br></pre></td></tr></table></figure><p>如果没有任何输出，就没任何问题。</p><h3 id="更新国内镜像源"><a href="#更新国内镜像源" class="headerlink" title="更新国内镜像源"></a>更新国内镜像源</h3><p>设置仓库地址为国内镜像源，下载更快。通过下面的命令自动选择当前延迟最低的站点：</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">reflector --country China --age 72 --sort rate --protocol https --save /etc/pacman.d/mirrorlist</span><br></pre></td></tr></table></figure><h3 id="更新软件仓库"><a href="#更新软件仓库" class="headerlink" title="更新软件仓库"></a>更新软件仓库</h3><p>同步之后刷新本地的仓库索引</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pacman -Sy</span><br></pre></td></tr></table></figure><h3 id="分区"><a href="#分区" class="headerlink" title="分区"></a>分区</h3><p>查看所有的硬盘信息，确定要安装到哪里</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">fdisk -l</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><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"># fdisk -l</span><br><span class="line">Disk /dev/sda: 931.51 GiB, 1000204886016 bytes, 1953525168 sectors</span><br><span class="line">Disk model: ST1000LM048-2E71</span><br><span class="line">Units: sectors of 1 * 512 = 512 bytes</span><br><span class="line">Sector size (logical/physical): 512 bytes / 4096 bytes</span><br><span class="line">I/O size (minimum/optimal): 4096 bytes / 4096 bytes</span><br><span class="line">Disklabel type: gpt</span><br><span class="line">Disk identifier: C62A2466-725D-4A41-8476-98A9686C930E</span><br><span class="line"></span><br><span class="line">Device     Start        End    Sectors   Size Type</span><br><span class="line">/dev/sda1     34      32767      32734    16M Microsoft reserved</span><br><span class="line">/dev/sda2  32768 1953521663 1953488896 931.5G Microsoft basic data</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">Disk /dev/nvme0n1: 953.87 GiB, 1024209543168 bytes, 2000409264 sectors</span><br><span class="line">Disk model: J.ZAO QL SERIES 1TB SSD</span><br><span class="line">Units: sectors of 1 * 512 = 512 bytes</span><br><span class="line">Sector size (logical/physical): 512 bytes / 512 bytes</span><br><span class="line">I/O size (minimum/optimal): 512 bytes / 512 bytes</span><br><span class="line">Disklabel type: gpt</span><br><span class="line">Disk identifier: A3CB2D7E-242B-46AC-AB1E-B80B7CE893A1</span><br><span class="line"></span><br><span class="line">Device         Start        End    Sectors   Size Type</span><br><span class="line">/dev/nvme0n1p1  4096 2000408575 2000404480 953.9G Microsoft basic data</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">Disk /dev/nvme1n1: 953.87 GiB, 1024209543168 bytes, 2000409264 sectors</span><br><span class="line">Disk model: INTEL SSDPEKNW010T8                     </span><br><span class="line">Units: sectors of 1 * 512 = 512 bytes</span><br><span class="line">Sector size (logical/physical): 512 bytes / 512 bytes</span><br><span class="line">I/O size (minimum/optimal): 512 bytes / 512 bytes</span><br><span class="line">Disklabel type: gpt</span><br><span class="line">Disk identifier: 8335BD2B-4BC6-449F-97B8-9921C663CC44</span><br><span class="line"></span><br><span class="line">Device           Start        End    Sectors   Size Type</span><br><span class="line">/dev/nvme1n1p1    2048    1026047    1024000   500M EFI System</span><br><span class="line">/dev/nvme1n1p2 1026048 2000408575 1999382528 953.4G Linux filesystem</span><br></pre></td></tr></table></figure><p>因为我的设备有三块硬盘，所以会显示三个，然后我想安装到其中的 <code>/dev/nvme1n1</code> ，然后执行下面的命令</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cfdisk /dev/nvme1n1</span><br></pre></td></tr></table></figure><p><code>cfdisk</code> 命令会进入一个带简单的 GUI 的页面分区，首先最好删除所有的分区，然后划分一个 500M 空间的分区， <code>Type</code> 设置为 <code>EFI System</code> ，剩下的空间按照需要再分一个较大的空间，<code>Type</code> 设置为 <code>Linux Filesystem</code> 即可。然后选择 <code>Write</code> 写入后退出即可。</p><div class="tag-plugin colorful note" ><div class="title">注意</div><div class="body"><p>可能也会包括移动设备，注意观察每个盘的空间大小</p></div></div><h3 id="格式化刚才的分区"><a href="#格式化刚才的分区" class="headerlink" title="格式化刚才的分区"></a>格式化刚才的分区</h3><h4 id="EFI-分区格式化"><a href="#EFI-分区格式化" class="headerlink" title="EFI 分区格式化"></a>EFI 分区格式化</h4><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mkfs.fat -F32 /dev/nvme1n1p1</span><br></pre></td></tr></table></figure><h4 id="格式化根目录分区"><a href="#格式化根目录分区" class="headerlink" title="格式化根目录分区"></a>格式化根目录分区</h4><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mkfs.f2fs -f -l Archlinux /dev/nvme1n1p2</span><br></pre></td></tr></table></figure><blockquote><p>-f是强制执行，-l后面的是这个分区的Label也就是名字自己想写什么都可以, 类似windows中c、d盘前面的名字</p></blockquote><h3 id="挂载"><a href="#挂载" class="headerlink" title="挂载"></a>挂载</h3><p>分区之后，将硬盘挂载到当前的 Live 系统。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">mount /dev/nvme1n1p2 /mnt</span><br><span class="line">mkdir /mnt/boot</span><br><span class="line">mount /dev/nvme1n1p1 /mnt/boot</span><br></pre></td></tr></table></figure><p><code>/mnt</code> 就是你将要安装的系统的根目录，然后将 EFI 目录挂载到 boot 目录。</p><h3 id="安装基础系统"><a href="#安装基础系统" class="headerlink" title="安装基础系统"></a>安装基础系统</h3><p>现在开始，就是真正安装你的系统了，通过 Live 系统中的 pacstrap 脚本将内核等基础软件安装的目标硬盘。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pacstrap /mnt linux linux-firmware linux-headers base base-devel bash-completion vim git intel-ucode f2fs-tools networkmanager wqy-zenhei openssh</span><br></pre></td></tr></table></figure><ul><li><p>如果你的跟分区是f2fs，<code>f2fs-tools</code>是必须的</p></li><li><p><code>intel-ucode</code>是根据你的cpu来的如果你的是amd的那就是<code>amd-ucode</code></p></li><li><p><code>networkmanager</code>也是必须的，因为你要联网，而且plasma和gnome都通过是<code>networkmanager</code>来管理网络的</p></li><li><p><code>wqy-zenhei</code>是中文字体，避免安装界面后乱码</p></li></ul><h3 id="生成自动挂载配置"><a href="#生成自动挂载配置" class="headerlink" title="生成自动挂载配置"></a>生成自动挂载配置</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">genfstab -U /mnt &gt;&gt; /mnt/etc/fstab</span><br><span class="line">cat /mnt/etc/fstab</span><br></pre></td></tr></table></figure><p>第一个命令是生成开机自动挂载配置，生成完之后一定要查看一下内容，确定是你刚才挂载的硬盘，否则会无法启动。</p><h2 id="初始化系统"><a href="#初始化系统" class="headerlink" title="初始化系统"></a>初始化系统</h2><p>现在开始系统运行所需要的包括内核等基础软件已经安装完成，需要通过 <code>arch-chroot</code> 切换到新系统来初始化。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">arch-chroot /mnt</span><br></pre></td></tr></table></figure><blockquote><p><code>chroot</code> 指的就是以 root 身份进入当前挂载的系统，后续如果系统出现问题，也可以通过 chroot 来进行修复。</p></blockquote><h3 id="设置时区"><a href="#设置时区" class="headerlink" title="设置时区"></a>设置时区</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime</span><br><span class="line">hwclock --systohc</span><br></pre></td></tr></table></figure><h3 id="设置系统语言"><a href="#设置系统语言" class="headerlink" title="设置系统语言"></a>设置系统语言</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">vim /etc/locale.gen</span><br></pre></td></tr></table></figure><p>在当前文件中取消注释<code>en_US.UTF-8 UTF-8</code> 和 <code>zh_CN.UTF-8 UTF-8</code> 这两行，然后保存退出。</p><p>然后执行</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">locale-gen</span><br></pre></td></tr></table></figure><p>初始化语言。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">echo &quot;en_US.UTF-8&quot; &gt; /etc/locale.conf</span><br></pre></td></tr></table></figure><p>这里设置英语为默认语言。</p><blockquote><p>不推荐在这里设置系统默认语言为中文，这样会生成很多中文路径，后续安装会比较麻烦。</p></blockquote><h3 id="设置必要的系统启动项"><a href="#设置必要的系统启动项" class="headerlink" title="设置必要的系统启动项"></a>设置必要的系统启动项</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">systemctl enable sshd NetworkManager</span><br></pre></td></tr></table></figure><h3 id="使用-systemd-boot-创建系统引导"><a href="#使用-systemd-boot-创建系统引导" class="headerlink" title="使用 systemd-boot 创建系统引导"></a>使用 systemd-boot 创建系统引导</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">bootctl install</span><br></pre></td></tr></table></figure><p>编辑引导内容</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">vim /boot/loader/loader.conf</span><br></pre></td></tr></table></figure><p>文件内容参考下面</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">default arch</span><br><span class="line">timeout 5</span><br><span class="line">console-mode max</span><br><span class="line">editor no</span><br></pre></td></tr></table></figure><ul><li>default arch就是默认启动 &#x2F;boot&#x2F;loader&#x2F;entries&#x2F;arch.conf</li><li>timeout 5 就是引导是等候按键输入的时间，如果是单系统可以设置为0</li></ul><p>然后编辑<code>/boot/loader/entries/arch.conf</code> 文件</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">title       Arch Linux</span><br><span class="line">linux       /vmlinuz-linux</span><br><span class="line">initrd      /intel-ucode.img</span><br><span class="line">initrd      /initramfs-linux.img</span><br><span class="line">options     root=UUID=7a6f4b50-3914-4745-b907-a11a26258215 rw quiet splash vt.global_cursor_default=0</span><br></pre></td></tr></table></figure><ul><li><p>title可以随便写，意思就是当前系统的命名</p></li><li><p>linux 这里是linux的内核，通过bootctl install就已经把内核复制到了&#x2F;boot下</p></li><li><p>initrd 这是要加载的模块，我这里加了intel-ucode.img和initramfs-linux.img</p><ul><li>initramfs-linux.img是启动系统必须要的模块，它在内核启动后第一个执行</li><li>如果你是amd的cpu可以把intel-ucode换成amd-ucode.img</li></ul></li><li><p>options 这是启动附加的参数</p><ul><li>root&#x3D;UUID&#x3D;7a6f4b50-3914-4745-b907-a11a26258215 这里指定了根目录分区，UUID为查找根目录分区方式，后面7a6f4b50-3914-4745-b907-a11a26258215为根目录分区的UUID</li><li>rw 这个内核参数设置根目录分区为可写可读</li><li>quiet splash vt.global_cursor_default&#x3D;0 这一堆设置安静的引导，也就是没有引导输出在显示器上，直到引导完成进入登陆页面</li></ul></li></ul><blockquote><p>在 vim 中执行 <code>:r!blkid</code> 可以输出当前硬盘信息</p><p>删除其他的，保留UUID即可</p></blockquote><h3 id="添加-pacman-的-hooks-文件，这样每次更新内核都会自动更新-efi-分区"><a href="#添加-pacman-的-hooks-文件，这样每次更新内核都会自动更新-efi-分区" class="headerlink" title="添加 pacman 的 hooks 文件，这样每次更新内核都会自动更新 efi 分区"></a>添加 pacman 的 hooks 文件，这样每次更新内核都会自动更新 efi 分区</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">mkdir /etc/pacman.d/hooks</span><br><span class="line">vim /etc/pacman.d/hooks/100-systemd-boot.hook</span><br></pre></td></tr></table></figure><p>文件内容如下</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">[Trigger]</span><br><span class="line">Type = Package</span><br><span class="line">Operation = Upgrade</span><br><span class="line">Target = systemd</span><br><span class="line"></span><br><span class="line">[Action]</span><br><span class="line">Description = Gracefully upgrading systemd-boot...</span><br><span class="line">When = PostTransaction</span><br><span class="line">Exec = /usr/bin/systemctl restart systemd-boot-update.service</span><br></pre></td></tr></table></figure><h2 id="设置用户"><a href="#设置用户" class="headerlink" title="设置用户"></a>设置用户</h2><h3 id="设置-root-密码"><a href="#设置-root-密码" class="headerlink" title="设置 root 密码"></a>设置 root 密码</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">passwd</span><br></pre></td></tr></table></figure><h3 id="创建自己的用户"><a href="#创建自己的用户" class="headerlink" title="创建自己的用户"></a>创建自己的用户</h3><p>因为 root 的权限太高，日常使用中<strong>非常容易</strong>误操作产生不可逆的破坏，所以一定要使用一个低权限账号来日常使用。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">useradd -m chaofan</span><br></pre></td></tr></table></figure><h3 id="添加用户组"><a href="#添加用户组" class="headerlink" title="添加用户组"></a>添加用户组</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">usermod -aG wheel,users,storage,power,lp,adm,optical chaofan</span><br></pre></td></tr></table></figure><h3 id="添加-sudo-权限"><a href="#添加-sudo-权限" class="headerlink" title="添加 sudo 权限"></a>添加 sudo 权限</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">visudo</span><br></pre></td></tr></table></figure><p>操作方式和 vim 相同，取消注释 <code>wheel ALL=(ALL:ALL) ALL</code> 这一行即可。</p><h3 id="设置当前用户密码"><a href="#设置当前用户密码" class="headerlink" title="设置当前用户密码"></a>设置当前用户密码</h3><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">passwd chaofan</span><br></pre></td></tr></table></figure><p>配置完成后，重启即可</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">exit</span><br><span class="line">reboot</span><br></pre></td></tr></table></figure><p>重启后记得拔掉 U盘，进入新系统。</p><h1 id="安装桌面环境"><a href="#安装桌面环境" class="headerlink" title="安装桌面环境"></a>安装桌面环境</h1><p>重启后依然是熟悉的终端 <span class="tag-plugin emoji"><img no-lazy="" class="inline" src="https://gcore.jsdelivr.net/gh/cdn-x/emoji/tieba/$滑稽.png"/></span></p><p>输入刚才创建的用户名和密码登陆即可，然后重新连接好网络。</p><h2 id="设置-AUR"><a href="#设置-AUR" class="headerlink" title="设置 AUR"></a>设置 AUR</h2><p>首先编辑 pacman 的配置文件。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo vim /etc/pacman.conf</span><br></pre></td></tr></table></figure><p>添加 archlinuxcn 源</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">[archlinuxcn]</span><br><span class="line">Server = https://mirrors.ustc.edu.cn/archlinuxcn/$arch</span><br></pre></td></tr></table></figure><p>然后取消 <code>[multlib]</code> 的注释。然后安装 <code>yay</code> 这是一个 AUR 管理器，可以安装来自 AUR 的包，使用方法和 pacman 相同。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo pacman -S yay</span><br></pre></td></tr></table></figure><h2 id="安装桌面环境-1"><a href="#安装桌面环境-1" class="headerlink" title="安装桌面环境"></a>安装桌面环境</h2><p>linux 下常见的桌面环境包括<strong>KDE</strong>和<strong>Gnome</strong>，我比较喜欢 gnome，整体风格更统一。</p><p>首先安装显卡驱动</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yay -S xf86-video-intel vulkan-driver mesa-vdpau libva-mesa-driver</span><br></pre></td></tr></table></figure><p>然后安装桌面环境</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yay -S gnome</span><br></pre></td></tr></table></figure><p>设置启动管理器开机自启</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo systemctl enable gdm</span><br></pre></td></tr></table></figure><p>最后重启，这样桌面环境就成功安装完毕了。</p>]]>
    </content>
    <id>https://chaofan.xyz/posts/e73ac284/</id>
    <link href="https://chaofan.xyz/posts/e73ac284/"/>
    <published>2023-01-07T17:00:35.000Z</published>
    <summary>
      <![CDATA[<p>因为前两天win11不知道怎么了把我的账户设置成了来宾账户，导致几乎所有软件都无法使用，遂决定格盘重装，想回到 Arch 好久了，借此机会重新体验一次。</p>]]>
    </summary>
    <title>记录一次Archlinux安装过程</title>
    <updated>2026-08-09T09:24:32.640Z</updated>
  </entry>
  <entry>
    <author>
      <name>chaofan</name>
      <email>chaofanxy@outlook.com</email>
    </author>
    <category term="Java" scheme="https://chaofan.xyz/categories/Java/"/>
    <category term="缓存" scheme="https://chaofan.xyz/tags/%E7%BC%93%E5%AD%98/"/>
    <category term="Caffeine" scheme="https://chaofan.xyz/tags/Caffeine/"/>
    <category term="Guava" scheme="https://chaofan.xyz/tags/Guava/"/>
    <content>
      <![CDATA[<p>Caffeine是使用Java8对Guava缓存的重写版本，在Spring Boot 2.0中取代Guava。如果出现Caffeine，CaffeineCacheManager将会自动配置。</p><span id="more"></span><p>Google Guava Cache是一种非常优秀本地缓存解决方案，提供了基于容量，时间和引用的缓存回收方式，他的优点是封装了get，put操作；提供<strong>线程安全</strong>的缓存操作；提供<strong>过期策略</strong>；提供<strong>回收策略</strong>；<strong>缓存监控</strong>。当缓存的数据超过最大值时，使用LRU算法替换。这一篇我们将要谈到一个新的本地缓存框架：Caffeine Cache。它也是站在巨人的肩膀上-Guava Cache，借着他的思想优化了算法发展而来。</p><p><strong>为什么需要本地缓存</strong></p><blockquote><p>相对于IO操作，速度快，效率高<br>相对于Redis，Redis是一种优秀的<strong>分布式</strong>缓存实现，但受限于网卡等原因，远水救不了近火</p></blockquote><p><strong>什么时候用</strong></p><ul><li>愿意消耗一些内存空间来提升速度</li><li>预料到某些键会被多次查询</li><li>缓存中存放的数据总量不会超出内存容量</li></ul><p><strong>怎么用</strong></p><ul><li>设置缓存容量</li><li>设置超时时间</li><li>提供移除监听器</li><li>提供缓存加载器</li><li>构建缓存</li></ul><h2 id="Why-Caffeine"><a href="#Why-Caffeine" class="headerlink" title="Why Caffeine"></a>Why Caffeine</h2><ol><li>Caffine 在算法上的优点</li></ol><p>常见的缓存淘汰算法还有<strong>LRU</strong>, <strong>FIFO</strong>, <strong>LFU</strong>:</p><h3 id="LRU"><a href="#LRU" class="headerlink" title="LRU"></a>LRU</h3><blockquote><p>LRU：最近最少使用算法，每次访问数据都会将其放在我们的队尾，如果需要淘汰数据，就只需要淘汰队首即可。仍然有个问题，如果有个数据在 1 分钟访问了 1000次，再后 1 分钟没有访问这个数据，但是有其他的数据访问，就导致了我们这个热点数据被淘汰。</p></blockquote><p>LRU的优点和局限性：LRU可以很好的应对突发流量的情况，因为他不需要累计数据频率。但LRU通过历史数据来预测未来是局限的，它会认为最后到来的数据是最可能被再次访问的，从而给与它最高的优先级。</p><h3 id="FIFO"><a href="#FIFO" class="headerlink" title="FIFO"></a>FIFO</h3><blockquote><p>FIFO：先进先出，在这种淘汰算法中，先进入缓存的会先被淘汰，会导致命中率很低。</p></blockquote><h3 id="LFU"><a href="#LFU" class="headerlink" title="LFU"></a>LFU</h3><blockquote><p>LFU：最近最少频率使用，利用额外的空间记录每个数据的使用频率，然后选出频率最低进行淘汰。这样就避免了 LRU 不能处理时间段的问题。<br>上面三种策略各有利弊，实现的成本也是一个比一个高，同时命中率也是一个比一个好。Guava Cache虽然有这么多的功能，但是本质上还是对LRU的封装，如果有更优良的算法，并且也能提供这么多功能，相比之下就相形见绌了。</p></blockquote><p>LFU的局限性：在 LFU 中只要数据访问模式的概率分布随时间保持不变时，其命中率就能变得非常高。比如有部新剧出来了，我们使用 LFU 给他缓存下来，这部新剧在这几天大概访问了几亿次，这个访问频率也在我们的 LFU 中记录了几亿次。但是新剧总会过气的，比如一个月之后这个新剧的前几集其实已经过气了，但是他的访问量的确是太高了，其他的电视剧根本无法淘汰这个新剧，所以在这种模式下是有局限性。</p><p>在现有算法的局限性下，会导致缓存数据的命中率或多或少的受损，而命中略又是缓存的重要指标。HighScalability网站刊登了一篇文章，由前Google工程师发明的<strong>W-TinyLFU</strong>: 一种现代的缓存 。Caffine Cache就是基于此算法而研发。Caffeine 因使用 Window TinyLfu 回收策略，提供了一个近乎最佳的命中率。</p><p>当数据的访问模式不随时间变化的时候，LFU的策略能够带来最佳的缓存命中率。然而LFU有两个缺点：</p><ol><li><p>它需要给每个记录项维护频率信息，每次访问都需要更新，这是个巨大的开销；</p></li><li><p>如果数据访问模式随时间有变，LFU的频率信息无法随之变化，因此早先频繁访问的记录可能会占据缓存，而后期访问较多的记录则无法被命中。</p></li></ol><p>因此，大多数的缓存设计都是基于LRU或者其变种来进行的。相比之下，LRU并不需要维护昂贵的缓存记录元信息，同时也能够反应随时间变化的数据访问模式。然而，在许多负载之下，LRU依然需要更多的空间才能做到跟LFU一致的缓存命中率。因此，一个“现代”的缓存，应当能够综合两者的长处。</p><h2 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h2><p>Maven</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.github.ben-manes.caffeine<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>caffeine<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.1.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></pre></td></tr></table></figure><p>Gradle (Kotlin DSL)</p><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">implementation(<span class="string">&quot;com.github.ben-manes.caffeine:caffeine:3.1.2&quot;</span>)</span><br></pre></td></tr></table></figure><h3 id="手动加载"><a href="#手动加载" class="headerlink" title="手动加载"></a>手动加载</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> Object <span class="title function_">manulOperator</span><span class="params">(String key)</span> &#123;</span><br><span class="line">    Cache&lt;String, Object&gt; cache = Caffeine.newBuilder()</span><br><span class="line">        .expireAfterWrite(<span class="number">1</span>, TimeUnit.SECONDS)</span><br><span class="line">        .expireAfterAccess(<span class="number">1</span>, TimeUnit.SECONDS)</span><br><span class="line">        .maximumSize(<span class="number">10</span>)</span><br><span class="line">        .build();</span><br><span class="line">    <span class="comment">//如果一个key不存在，那么会进入指定的函数生成value</span></span><br><span class="line">    <span class="type">Object</span> <span class="variable">value</span> <span class="operator">=</span> cache.get(key, t -&gt; setValue(key).apply(key));</span><br><span class="line">    cache.put(<span class="string">&quot;hello&quot;</span>,value);</span><br><span class="line"></span><br><span class="line">    <span class="comment">//判断是否存在如果不存返回null</span></span><br><span class="line">    <span class="type">Object</span> <span class="variable">ifPresent</span> <span class="operator">=</span> cache.getIfPresent(key);</span><br><span class="line">    <span class="comment">//移除一个key</span></span><br><span class="line">    cache.invalidate(key);</span><br><span class="line">    <span class="keyword">return</span> value;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> Function&lt;String, Object&gt; <span class="title function_">setValue</span><span class="params">(String key)</span>&#123;</span><br><span class="line">    <span class="keyword">return</span> t -&gt; key + <span class="string">&quot;value&quot;</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可配置项</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><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">initialCapacity=[integer]: 初始的缓存空间大小</span><br><span class="line"></span><br><span class="line">maximumSize=[long]: 缓存的最大条数</span><br><span class="line"></span><br><span class="line">maximumWeight=[long]: 缓存的最大权重</span><br><span class="line"></span><br><span class="line">expireAfterAccess=[duration]: 最后一次写入或访问后经过固定时间过期</span><br><span class="line"></span><br><span class="line">expireAfterWrite=[duration]: 最后一次写入后经过固定时间过期</span><br><span class="line"></span><br><span class="line">refreshAfterWrite=[duration]: 创建缓存或者最近一次更新缓存后经过固定的时间间隔，刷新缓存</span><br><span class="line"></span><br><span class="line">weakKeys: 打开key的弱引用</span><br><span class="line"></span><br><span class="line">weakValues：打开value的弱引用</span><br><span class="line"></span><br><span class="line">softValues：打开value的软引用</span><br><span class="line"></span><br><span class="line">recordStats：开发统计功能</span><br><span class="line"></span><br><span class="line">注意：</span><br><span class="line"></span><br><span class="line">expireAfterWrite和expireAfterAccess同时存在时，以expireAfterWrite为准。</span><br><span class="line"></span><br><span class="line">maximumSize和maximumWeight不可以同时使用</span><br><span class="line"></span><br><span class="line">weakValues和softValues不可以同时使用</span><br></pre></td></tr></table></figure><h3 id="同步加载"><a href="#同步加载" class="headerlink" title="同步加载"></a>同步加载</h3><p>构造Cache时候，build方法传入一个CacheLoader实现类。实现load方法，通过key加载value。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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> Object <span class="title function_">syncOperator</span><span class="params">(String key)</span>&#123;</span><br><span class="line">    LoadingCache&lt;String, Object&gt; cache = Caffeine.newBuilder()</span><br><span class="line">        .maximumSize(<span class="number">100</span>)</span><br><span class="line">        .expireAfterWrite(<span class="number">1</span>, TimeUnit.MINUTES)</span><br><span class="line">        .build(k -&gt; setValue(key).apply(key));</span><br><span class="line">    <span class="keyword">return</span> cache.get(key);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="异步加载"><a href="#异步加载" class="headerlink" title="异步加载"></a>异步加载</h3><p>AsyncLoadingCache是继承自LoadingCache类的，异步加载使用Executor去调用方法并返回一个CompletableFuture。异步加载缓存使用了响应式编程模型。</p><p>如果要以同步方式调用时，应提供CacheLoader。要以异步表示时，应该提供一个AsyncCacheLoader，并返回一个CompletableFuture。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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> Object <span class="title function_">asyncOperator</span><span class="params">(String key)</span>&#123;</span><br><span class="line">    AsyncLoadingCache&lt;String, Object&gt; cache = Caffeine.newBuilder()</span><br><span class="line">        .maximumSize(<span class="number">100</span>)</span><br><span class="line">        .expireAfterWrite(<span class="number">1</span>, TimeUnit.MINUTES)</span><br><span class="line">        .buildAsync(k -&gt; setAsyncValue(key).get());</span><br><span class="line">    <span class="keyword">return</span> cache.get(key);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> CompletableFuture&lt;Object&gt; <span class="title function_">setAsyncValue</span><span class="params">(String key)</span>&#123;</span><br><span class="line">    <span class="keyword">return</span> CompletableFuture.supplyAsync(() -&gt; &#123;</span><br><span class="line">        <span class="keyword">return</span> key + <span class="string">&quot;value&quot;</span>;</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>Caffeine提供了3种回收策略：基于大小回收，基于时间回收，基于引用回收。</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 根据缓存的计数进行驱逐</span></span><br><span class="line">LoadingCache&lt;String, Object&gt; cache = Caffeine.newBuilder()</span><br><span class="line">    .maximumSize(<span class="number">10000</span>)</span><br><span class="line">    .build(key -&gt; function(key));</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// 根据缓存的权重来进行驱逐（权重只是用于确定缓存大小，不会用于决定该缓存是否被驱逐）</span></span><br><span class="line">LoadingCache&lt;String, Object&gt; cache1 = Caffeine.newBuilder()</span><br><span class="line">    .maximumWeight(<span class="number">10000</span>)</span><br><span class="line">    .weigher(key -&gt; function1(key))</span><br><span class="line">    .build(key -&gt; function(key));</span><br></pre></td></tr></table></figure><p>maximumWeight与maximumSize不可以同时使用。</p><h3 id="基于时间的过期方式"><a href="#基于时间的过期方式" class="headerlink" title="基于时间的过期方式"></a>基于时间的过期方式</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 基于固定的到期策略进行退出</span></span><br><span class="line">LoadingCache&lt;String, Object&gt; cache = Caffeine.newBuilder()</span><br><span class="line">    .expireAfterAccess(<span class="number">5</span>, TimeUnit.MINUTES)</span><br><span class="line">    .build(key -&gt; function(key));</span><br><span class="line">LoadingCache&lt;String, Object&gt; cache1 = Caffeine.newBuilder()</span><br><span class="line">    .expireAfterWrite(<span class="number">10</span>, TimeUnit.MINUTES)</span><br><span class="line">    .build(key -&gt; function(key));</span><br><span class="line"></span><br><span class="line"><span class="comment">// 基于不同的到期策略进行退出</span></span><br><span class="line">LoadingCache&lt;String, Object&gt; cache2 = Caffeine.newBuilder()</span><br><span class="line">    .expireAfter(<span class="keyword">new</span> <span class="title class_">Expiry</span>&lt;String, Object&gt;() &#123;</span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> <span class="type">long</span> <span class="title function_">expireAfterCreate</span><span class="params">(String key, Object value, <span class="type">long</span> currentTime)</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> TimeUnit.SECONDS.toNanos(seconds);</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">long</span> <span class="title function_">expireAfterUpdate</span><span class="params">(<span class="meta">@Nonnull</span> String s, <span class="meta">@Nonnull</span> Object o, <span class="type">long</span> l, <span class="type">long</span> l1)</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> <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="type">long</span> <span class="title function_">expireAfterRead</span><span class="params">(<span class="meta">@Nonnull</span> String s, <span class="meta">@Nonnull</span> Object o, <span class="type">long</span> l, <span class="type">long</span> l1)</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;).build(key -&gt; function(key));</span><br></pre></td></tr></table></figure><p>Caffeine提供了三种定时驱逐策略：</p><p>expireAfterAccess(long, TimeUnit):在最后一次访问或者写入后开始计时，在指定的时间后过期。假如一直有请求访问该key，那么这个缓存将一直不会过期。expireAfterWrite(long, TimeUnit): 在最后一次写入缓存后开始计时，在指定的时间后过期。expireAfter(Expiry): 自定义策略，过期时间由Expiry实现独自计算。缓存的删除策略使用的是惰性删除和定时删除。这两个删除策略的时间复杂度都是O(1)。</p><h3 id="基于引用的过期方式"><a href="#基于引用的过期方式" class="headerlink" title="基于引用的过期方式"></a>基于引用的过期方式</h3><p>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><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><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="comment">// 当key和value都没有引用时驱逐缓存</span></span><br><span class="line">LoadingCache&lt;String, Object&gt; cache = Caffeine.newBuilder()</span><br><span class="line">    .weakKeys()</span><br><span class="line">    .weakValues()</span><br><span class="line">    .build(key -&gt; function(key));</span><br><span class="line"></span><br><span class="line"><span class="comment">// 当垃圾收集器需要释放内存时驱逐</span></span><br><span class="line">LoadingCache&lt;String, Object&gt; cache1 = Caffeine.newBuilder()</span><br><span class="line">    .softValues()</span><br><span class="line">    .build(key -&gt; function(key));</span><br></pre></td></tr></table></figure><blockquote><p>注意：AsyncLoadingCache不支持弱引用和软引用。</p></blockquote><p>Caffeine.weakKeys()：使用弱引用存储key。如果没有其他地方对该key有强引用，那么该缓存就会被垃圾回收器回收。由于垃圾回收器只依赖于身份(identity)相等，因此这会导致整个缓存使用身份 (&#x3D;&#x3D;) 相等来比较 key，而不是使用 equals()。</p><p>Caffeine.weakValues() ：使用弱引用存储value。如果没有其他地方对该value有强引用，那么该缓存就会被垃圾回收器回收。由于垃圾回收器只依赖于身份(identity)相等，因此这会导致整个缓存使用身份 (&#x3D;&#x3D;) 相等来比较 key，而不是使用 equals()。</p><p>Caffeine.softValues() ：使用软引用存储value。当内存满了过后，软引用的对象以将使用最近最少使用(least-recently-used ) 的方式进行垃圾回收。由于使用软引用是需要等到内存满了才进行回收，所以我们通常建议给缓存配置一个使用内存的最大值。softValues() 将使用身份相等(identity) (&#x3D;&#x3D;) 而不是equals() 来比较值。</p><p><strong>Caffeine.weakValues()和Caffeine.softValues()不可以一起使用。</strong></p><h2 id="移除事件监听"><a href="#移除事件监听" class="headerlink" title="移除事件监听"></a>移除事件监听</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Cache&lt;String, Object&gt; cache = Caffeine.newBuilder()</span><br><span class="line">    .removalListener((String key, Object value, RemovalCause cause) -&gt;</span><br><span class="line">                     System.out.printf(<span class="string">&quot;Key %s was removed (%s)%n&quot;</span>, key, cause))</span><br><span class="line">    .build();</span><br></pre></td></tr></table></figure><h2 id="写入外部存储"><a href="#写入外部存储" class="headerlink" title="写入外部存储"></a>写入外部存储</h2><p>CacheWriter 方法可以将缓存中所有的数据写入到第三方。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><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">LoadingCache&lt;String, Object&gt; cache2 = Caffeine.newBuilder()</span><br><span class="line">    .writer(<span class="keyword">new</span> <span class="title class_">CacheWriter</span>&lt;String, Object&gt;() &#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_">write</span><span class="params">(String key, Object value)</span> &#123;</span><br><span class="line">            <span class="comment">// 写入到外部存储</span></span><br><span class="line">        &#125;</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_">delete</span><span class="params">(String key, Object value, RemovalCause cause)</span> &#123;</span><br><span class="line">            <span class="comment">// 删除外部存储</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;)</span><br><span class="line">    .build(key -&gt; function(key));</span><br></pre></td></tr></table></figure><p>如果你有多级缓存的情况下，这个方法还是很实用。</p><p><strong>注意：CacheWriter不能与弱键或AsyncLoadingCache一起使用。</strong></p><h2 id="统计"><a href="#统计" class="headerlink" title="统计"></a>统计</h2><p>与Guava Cache的统计一样。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><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">Cache&lt;String, Object&gt; cache = Caffeine.newBuilder()</span><br><span class="line">    .maximumSize(<span class="number">10_000</span>)</span><br><span class="line">    .recordStats()</span><br><span class="line">    .build();</span><br></pre></td></tr></table></figure><p>通过使用Caffeine.recordStats(), 可以转化成一个统计的集合. 通过 Cache.stats() 返回一个CacheStats。CacheStats提供以下统计方法：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">hitRate(): 返回缓存命中率</span><br><span class="line"></span><br><span class="line">evictionCount(): 缓存回收数量</span><br><span class="line"></span><br><span class="line">averageLoadPenalty(): 加载新值的平均时间</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://chaofan.xyz/posts/c9787f55/</id>
    <link href="https://chaofan.xyz/posts/c9787f55/"/>
    <published>2022-12-10T22:58:42.000Z</published>
    <summary>
      <![CDATA[<p>Caffeine是使用Java8对Guava缓存的重写版本，在Spring Boot 2.0中取代Guava。如果出现Caffeine，CaffeineCacheManager将会自动配置。</p>]]>
    </summary>
    <title>Caffeine-高性能Java本地缓存</title>
    <updated>2026-08-09T09:24:32.640Z</updated>
  </entry>
  <entry>
    <author>
      <name>chaofan</name>
      <email>chaofanxy@outlook.com</email>
    </author>
    <category term="操作系统" scheme="https://chaofan.xyz/categories/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/"/>
    <category term="缓冲" scheme="https://chaofan.xyz/tags/%E7%BC%93%E5%86%B2/"/>
    <category term="C语言" scheme="https://chaofan.xyz/tags/C%E8%AF%AD%E8%A8%80/"/>
    <category term="攻击" scheme="https://chaofan.xyz/tags/%E6%94%BB%E5%87%BB/"/>
    <content>
      <![CDATA[<p>C语言对于数组引用不进行任何边界检查，而且局部变量和状态信息都放在栈中。这两种情况结合到一起就能导致严重的程序错误，比如对越界的数组元素的写操作。</p><span id="more"></span><h2 id="缓冲区溢出"><a href="#缓冲区溢出" class="headerlink" title="缓冲区溢出"></a>缓冲区溢出</h2><p>一种常见的状态破坏被称为<strong>缓冲区溢出</strong>。一般在栈中分配一个字符数组来保存一个字符串，但是字符串的长度超出了为数组分配的空间。就像下面这样：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Implementation of library function gets()</span></span><br><span class="line"><span class="type">char</span> *<span class="title function_">gets</span><span class="params">(<span class="type">char</span> *s)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="type">int</span> c;</span><br><span class="line">    <span class="type">char</span> *dest = s;</span><br><span class="line">    <span class="keyword">while</span> ((c = getchar()) != <span class="string">&#x27;\n&#x27;</span> &amp;&amp; c != EOF)</span><br><span class="line">        *dest++ = c;</span><br><span class="line">    <span class="keyword">if</span> (c == EOF &amp;&amp; dest == s)</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line">    *dest++ = <span class="string">&#x27;\0&#x27;</span>;</span><br><span class="line">    <span class="keyword">return</span> s;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// Read input line and write it back</span></span><br><span class="line"><span class="type">void</span> <span class="title function_">echo</span><span class="params">()</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="type">char</span> buf[<span class="number">8</span>]; <span class="comment">// Way to small!!</span></span><br><span class="line">    gets(buf);</span><br><span class="line">    <span class="built_in">puts</span>(buf);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这是库函数<code>gets</code>的一个实现。它从标准输入读入一行，在遇到一个回车换行字符或者某个错误情况时停止。然后把这个字符串复制到参数s指定的位置，并在结尾加上null字符。<code>echo</code>调用了这个函数然后送回到标准输出。</p><p>这个函数的问题是它没有办法确定字符串是否有足够的空间，我们将<code>buf</code>设置的比较小，这样任何长度超过7的字符串都会导致越界。</p><p>检查gcc为echo产生的汇编代码：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">echo:</span><br><span class="line">subq$24, %rsp;Allocate 24 bytes on stack</span><br><span class="line">movq%rsp, %rdi;Compute buf as %rsp</span><br><span class="line">callgets;Call gets</span><br><span class="line">movq%rsp, %rdi;Compute buf as %rsp</span><br><span class="line">callputs;Call puts</span><br><span class="line">addq$24, %rsp;Deallocate stack space</span><br><span class="line">ret;Return</span><br></pre></td></tr></table></figure><p>该程序在栈上分配了24个字节。buf位于栈顶，然后把%rsp复制到%rdi作为调用gets和puts的参数。其中有16个字节未被使用。只要输入的字符串超过7个字节，过长的字符串就会覆盖栈上存储的某些信息。</p><table><thead><tr><th>输入的字符串数量</th><th>附加的被破坏的状态</th></tr></thead><tbody><tr><td>0-7</td><td>无</td></tr><tr><td>9-23</td><td>未被使用的栈空间</td></tr><tr><td>24-31</td><td>返回地址</td></tr><tr><td>32+</td><td>caller中保存的状态</td></tr></tbody></table><p>在23个字符之前都没有严重的后果，但是超过以后就会破坏存储的返回位置，ret指令会导致程序跳转到一个完全意想不到的位置。</p><p>缓冲区溢出的更加致命的问题就是会让程序执行它本来不会执行的代码。这是一种最常见的通过计算机网络攻击系统安全的方法。通常，输入一个字符串，这个字符串包含一些可执行的字节编码，称为<strong>攻击代码</strong>，还有一些字节会用一个指向攻击代码的指针覆盖返回地址。那么，执行ret的效果就是跳转到攻击代码。</p><p>常见的攻击形式有两种：</p><ul><li>使用系统调用启动一个shell程序给攻击者提供操作系统函数</li><li>执行一些未授权的任务，修复对栈的破坏，然后第二次执行ret指令，正常返回给调用者</li></ul><h2 id="对抗缓冲区溢出攻击"><a href="#对抗缓冲区溢出攻击" class="headerlink" title="对抗缓冲区溢出攻击"></a>对抗缓冲区溢出攻击</h2><p>现代编译器和操作系统实现了很多机制，以避免遭受这样的攻击。</p><h3 id="栈随机化"><a href="#栈随机化" class="headerlink" title="栈随机化"></a>栈随机化</h3><p>为了在系统中插入攻击代码，攻击者既要插入代码，也要插入指向这段代码的指针，这个指针也是攻击字符串的一部分。产生这个指针需要知道这个字符串放置的栈地址。在以前，程序的栈地址非常容易预测。对于所有运行同样程序和操作系统版本的系统来说，在不同的机器之间，栈的位置是相当固定的。因此攻击者可以确定一个常见的Web服务器所使用的栈空间，就可以设计一个在许多机器上都能实施的攻击。这种现象被称作<strong>安全单一化</strong>。</p><p>栈随机化的思想使得栈的位置在程序每次运行时都会有变化，这样在不同机器上执行同样的代码，他们的栈地址都是不同的。实现的方式是：程序开始时，在栈上分配一个0-n字节之间的随机大小的空间，程序不使用这段空间，但是它会导致每次执行后续的栈位置发生了变化。可以通过下面的代码确定栈的地址。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">long</span> local;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;local at %p\n&quot;</span>, &amp;local);</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在64位的操作系统上的范围大约是$2^{32}$。</p><p>在linux系统中，这类技术乘坐<strong>地址空间布局随机化(ASLR)</strong>，采用ASLR，每次运行时程序的不同部分，包括程序代码，库代码，栈，全局变量和堆数据，都会被加载到内存的不同区域。</p><blockquote><p>但是这种技术也有漏洞，攻击者可以在攻击代码前插入很长的一段<code>nop</code>。这个指令唯一作用就是让PC加一，指向下一条指令。只要攻击者能够猜中这段序列的某个地址，程序就会“滑过”这个序列。</p></blockquote><h3 id="栈破坏检测"><a href="#栈破坏检测" class="headerlink" title="栈破坏检测"></a>栈破坏检测</h3><p>在最新的GCC中加入了一种<strong>栈保护者(stack protector)<strong>机制来检测缓冲区越界。其思想时在栈帧中任何布局缓冲区和栈状态之家存储一个特殊的</strong>金丝雀(canary)<strong>值，也称作</strong>哨兵值(guard value)</strong>，在程序每次运行时随机产生，因此攻击者没有简单的办法能知道它时什么。在恢复寄存器状态和从函数返回之前，会检查这个值是否被修改。如果是，那么程序异常中止。</p><blockquote><p>可以通过<code>-fno-stack-protector</code>选项来阻止GCC产生这种代码。</p></blockquote><p>栈保护很好地防止了缓冲区溢出攻击破坏存储在程序栈上的状态。它只会带来很小的性能损失，特别是GCC只在函数中有局部char类型缓冲区的时候才插入这样的代码。</p><h3 id="限制可执行代码区域"><a href="#限制可执行代码区域" class="headerlink" title="限制可执行代码区域"></a>限制可执行代码区域</h3><p>典型的程序中，只有保存编译器产生的代码的那部分内存才需要是可执行的。其他部分被限制为只允许读和写。</p><p>有些类型的程序要求动态产生和执行代码的能力，比如使用JIT的Java动态的产生代码，以提高执行性能。是否能够将可执行代码限制在由编译器在创建原始程序时产生的那个部分中，取决于语言和操作系统。</p>]]>
    </content>
    <id>https://chaofan.xyz/posts/3319f49e/</id>
    <link href="https://chaofan.xyz/posts/3319f49e/"/>
    <published>2022-08-18T00:09:58.000Z</published>
    <summary>
      <![CDATA[<p>C语言对于数组引用不进行任何边界检查，而且局部变量和状态信息都放在栈中。这两种情况结合到一起就能导致严重的程序错误，比如对越界的数组元素的写操作。</p>]]>
    </summary>
    <title>缓冲区溢出攻击</title>
    <updated>2026-08-09T09:24:32.640Z</updated>
  </entry>
  <entry>
    <author>
      <name>chaofan</name>
      <email>chaofanxy@outlook.com</email>
    </author>
    <category term="操作系统" scheme="https://chaofan.xyz/categories/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/"/>
    <category term="内存" scheme="https://chaofan.xyz/tags/%E5%86%85%E5%AD%98/"/>
    <category term="C++" scheme="https://chaofan.xyz/tags/C/"/>
    <category term="结构体" scheme="https://chaofan.xyz/tags/%E7%BB%93%E6%9E%84%E4%BD%93/"/>
    <content>
      <![CDATA[<p>元素是按照定义顺序一个一个放到内存中去的，但并不是紧密排列的。从结构体存储的首地址开始，每个元素放置到内存中时，它都会认为内存是按照自己的大小（通常它为4或8）来划分的，因此元素放置的位置一定会在自己宽度的整数倍上开始，这就是所谓的内存对齐。</p><span id="more"></span><h2 id="什么是内存对齐"><a href="#什么是内存对齐" class="headerlink" title="什么是内存对齐"></a>什么是内存对齐</h2><p>元素是按照定义顺序一个一个放到内存中去的，但并不是紧密排列的。从结构体存储的首地址开始，每个元素放置到内存中时，它都会认为内存是按照自己的大小（通常它为4或8）来划分的，因此元素放置的位置一定会在自己宽度的整数倍上开始，这就是所谓的内存对齐。</p><blockquote><p>编译器为程序中的每个“数据单元”安排在适当的位置上。C语言允许你干预“内存对齐”。如果你想了解更加底层的秘密，“内存对齐”对你就不应该再模糊了。</p></blockquote><h2 id="例子"><a href="#例子" class="headerlink" title="例子"></a>例子</h2><p>理论上，int占4byte，char占一个byte，将他们放在一个结构体里面，这个结构体应该占5byte，但是实际上却不是这样，这就是内存对齐导致的。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="class"><span class="keyword">struct</span>&#123;</span></span><br><span class="line">    <span class="type">int</span> x;</span><br><span class="line">    <span class="type">char</span> y;</span><br><span class="line">&#125;Test;</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;%d\n&quot;</span>,<span class="keyword">sizeof</span>(Test)); <span class="comment">// 输出8不是5</span></span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="为什么要内存对齐"><a href="#为什么要内存对齐" class="headerlink" title="为什么要内存对齐"></a>为什么要内存对齐</h2><ol><li><strong>平台原因(移植原因)</strong>：不是所有的硬件平台都能访问任意地址上的任意数据的；某些硬件平台只能在某些地址处取某些特定类型的数据，否则抛出硬件异常。</li><li><strong>性能原因</strong>：数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于，为了访问未对齐的内存，处理器需要作两次内存访问；而对齐的内存访问仅需要一次访问。</li></ol><ul><li>假如没有内存对齐机制，数据可以任意存放，现在一个int变量存放在从地址1开始的联系四个字节地址中，该处理器去取数据时，要先从0地址开始读取第一个4字节块,剔除不想要的字节（0地址）,然后从地址4开始读取下一个4字节块,同样剔除不要的数据（5，6，7地址）,最后留下的两块数据合并放入寄存器。这需要做很多工作。</li><li>现在有了内存对齐的，int类型数据只能存放在按照对齐规则的内存中，比如说0地址开始的内存。那么现在该处理器在取数据时一次性就能将数据读出来了，而且不需要做额外的操作，提高了效率。</li></ul><h2 id="内存对齐规则"><a href="#内存对齐规则" class="headerlink" title="内存对齐规则"></a><strong>内存对齐规则</strong></h2><ol><li><strong>基本类型</strong>的对齐值就是其sizeof值;</li><li><strong>数据成员对齐规则</strong>：结构(struct)(或联合(union))的数据成员，第一个数据成员放在offset为0的地方，以后每个数据成员的对齐按照#pragma pack指定的数值和这个数据成员自身长度中，比较小的那个进行;</li><li><strong>结构(或联合)的整体对齐规则</strong>：在数据成员完成各自对齐之后，结构(或联合)本身也要进行对齐，对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中，比较小的那个进行;</li></ol><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> &#123;</span></span><br><span class="line">    <span class="type">int</span> i;    </span><br><span class="line">    <span class="type">char</span> c1;  </span><br><span class="line">    <span class="type">char</span> c2;  </span><br><span class="line">&#125;Test1;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> &#123;</span></span><br><span class="line">    <span class="type">char</span> c1;  </span><br><span class="line">    <span class="type">int</span> i;    </span><br><span class="line">    <span class="type">char</span> c2;  </span><br><span class="line">&#125;Test2;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> &#123;</span></span><br><span class="line">    <span class="type">char</span> c1;  </span><br><span class="line">    <span class="type">char</span> c2; </span><br><span class="line">    <span class="type">int</span> i;    </span><br><span class="line">&#125;Test3;</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;%d\n&quot;</span>,<span class="keyword">sizeof</span>(Test1));  <span class="comment">// 输出8</span></span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;%d\n&quot;</span>,<span class="keyword">sizeof</span>(Test2));  <span class="comment">// 输出12</span></span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;%d\n&quot;</span>,<span class="keyword">sizeof</span>(Test3));  <span class="comment">// 输出8</span></span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>默认#pragma pack(4)，且结构体中最长的数据类型为4个字节，所以有效对齐单位为4字节，下面根据上面所说的规则以第二个结构体来分析其内存布局： 首先使用规则1，对成员变量进行对齐：</p><ul><li>sizeof(c1) &#x3D; 1 &lt;&#x3D; 4(有效对齐位)，按照1字节对齐，占用第0单元；</li><li>sizeof(i) &#x3D; 4 &lt;&#x3D; 4(有效对齐位)，相对于结构体首地址的偏移要为4的倍数，占用第4，5，6，7单元；</li><li>sizeof(c2) &#x3D; 1 &lt;&#x3D; 4(有效对齐位)，相对于结构体首地址的偏移要为1的倍数，占用第8单元；</li></ul><p>然后使用规则2，对结构体整体进行对齐：</p><p>第二个结构体中变量i占用内存最大占4字节，而有效对齐单位也为4字节，两者较小值就是4字节。因此整体也是按照4字节对齐。由规则1得到s2占9个字节，此处再按照规则2进行整体的4字节对齐，所以整个结构体占用12个字节。</p><h2 id="修改C语言对齐方式"><a href="#修改C语言对齐方式" class="headerlink" title="修改C语言对齐方式"></a>修改C语言对齐方式</h2><p>更改C编译器的缺省字节对齐方式：</p><blockquote><p>在缺省情况下，C编译器为每一个变量或是数据单元按其自然对界条件分配空间。一般地，可以通过下面的方法来改变缺省的对界条件：</p></blockquote><ul><li>使用伪指令#pragma pack (n)，C编译器将按照n个字节对齐。</li><li>使用伪指令#pragma pack ()，取消自定义字节对齐方式。</li></ul><blockquote><p>另外，还有如下的一种方式：</p></blockquote><ul><li>__attribute((aligned (n)))，让所作用的结构成员对齐在n字节自然边界上。如果结构中有成员的长度大于n，则按照最大成员的长度来对齐。</li><li>attribute((packed))，取消结构在编译过程中的优化对齐，按照实际占用字节数进行对齐。</li></ul><p>不同平台上编译器的 pragma pack 默认值不同。而我们可以通过预编译命令#pragma pack(n), n&#x3D; 1,2,4,8,16来改变对齐系数。</p><p>例如，对于上个例子的三个结构体，如果前面加上#pragma pack(1)，那么此时有效对齐值为1字节，此时根据对齐规则，不难看出成员是连续存放的，三个结构体的大小都是6字节。</p><h2 id="修改C-内存对齐方式"><a href="#修改C-内存对齐方式" class="headerlink" title="修改C++内存对齐方式"></a>修改C++内存对齐方式</h2><p>c++11以后引入两个关键字 <code>alignas</code>与<code> alignof</code>。其中<code>alignof</code>可以计算出类型的对齐方式，<code>alignas</code>可以指定结构体的对齐方式。</p><p>但是<code>alignas</code>在某些情况下是不能使用的，具体见下面的例子:</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><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">struct</span> <span class="title class_">Info</span> &#123;</span><br><span class="line">  <span class="type">uint8_t</span> a;</span><br><span class="line">  <span class="type">uint16_t</span> b;</span><br><span class="line">  <span class="type">uint8_t</span> c;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">std::cout &lt;&lt; <span class="built_in">sizeof</span>(Info) &lt;&lt; std::endl;   <span class="comment">// 6  2 + 2 + 2</span></span><br><span class="line">std::cout &lt;&lt; <span class="built_in">alignof</span>(Info) &lt;&lt; std::endl;  <span class="comment">// 2</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">struct</span> <span class="title class_">alignas</span>(<span class="number">4</span>) Info2 &#123;</span><br><span class="line">  <span class="type">uint8_t</span> a;</span><br><span class="line">  <span class="type">uint16_t</span> b;</span><br><span class="line">  <span class="type">uint8_t</span> c;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">std::cout &lt;&lt; <span class="built_in">sizeof</span>(Info2) &lt;&lt; std::endl;   <span class="comment">// 8  4 + 4</span></span><br><span class="line">std::cout &lt;&lt; <span class="built_in">alignof</span>(Info2) &lt;&lt; std::endl;  <span class="comment">// 4</span></span><br></pre></td></tr></table></figure><h2 id="区别"><a href="#区别" class="headerlink" title="区别"></a>区别</h2><ul><li>#pragma pack (n)指定的是全局的内存对齐方式</li><li>alignas的粒度更小，修饰在结构体上就是指定结构体的内存对齐方式，修饰在变量上就是指定变量的内存对齐方式</li></ul>]]>
    </content>
    <id>https://chaofan.xyz/posts/615220dc/</id>
    <link href="https://chaofan.xyz/posts/615220dc/"/>
    <published>2022-08-09T22:15:07.000Z</published>
    <summary>
      <![CDATA[<p>元素是按照定义顺序一个一个放到内存中去的，但并不是紧密排列的。从结构体存储的首地址开始，每个元素放置到内存中时，它都会认为内存是按照自己的大小（通常它为4或8）来划分的，因此元素放置的位置一定会在自己宽度的整数倍上开始，这就是所谓的内存对齐。</p>]]>
    </summary>
    <title>结构体的内存对齐</title>
    <updated>2026-08-09T09:24:32.640Z</updated>
  </entry>
  <entry>
    <author>
      <name>chaofan</name>
      <email>chaofanxy@outlook.com</email>
    </author>
    <category term="操作系统" scheme="https://chaofan.xyz/categories/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/"/>
    <category term="Java" scheme="https://chaofan.xyz/tags/Java/"/>
    <category term="原子性" scheme="https://chaofan.xyz/tags/%E5%8E%9F%E5%AD%90%E6%80%A7/"/>
    <content>
      <![CDATA[<span id="more"></span><p>原子(atomic)指的是“不能被进一步分割的最小粒子”，原子操作指的是“不可被中断的一个或一系列操作”。在多处理器上实现原子操作就会变得很复杂。</p><h2 id="处理器如何实现原子操作"><a href="#处理器如何实现原子操作" class="headerlink" title="处理器如何实现原子操作"></a>处理器如何实现原子操作</h2><p>32位IA32处理器使用的是基于对缓存加锁或总线加锁的方式来实现多处理器之间的原子操作。首先处理器会自动保证基本的内存操作的原子性。处理器保证从系统内存中读取或者写入一个字节是原子的，意思是当一个处理器读取一个原子时，其他处理器不能访问这个字节的内存地址。现代的处理器能自动保证其原子性，比如跨总线宽度，跨多个缓存行和跨页表的访问。但是，处理器提供总线锁定和缓存锁定两个机制来保证内存操作的原子性。</p><h3 id="使用总线保证原子性"><a href="#使用总线保证原子性" class="headerlink" title="使用总线保证原子性"></a>使用总线保证原子性</h3><p>如果多个处理器同时对共享变量进行读改写操作（比如i++），那么共享变量就会被多个处理器同时进行操作，这样读改写操作就不是原子的，操作完之后共享变量的值会和期望的不一致。</p><blockquote><p>比如i&#x3D;1，进行两次i++操作，期望的结果是3，但是有可能结果是2</p></blockquote><p>原因可能是多个处理器同时从各自的缓存中读取变量i，分别进行加1操作，然后分别写入系统内存中。那么，想要保证读改写共享变量的操作是原子的，就必须保证CPU1读改写共享变量的时候，CPU2不能操作缓存了该共享变量内存地址的缓存。</p><p>所以处理器使用总线锁来解决这个问题。</p><p>总线锁就是使用处理器提供的一个LOCK#信号，当一个处理器在总线上输出此信号时，其他处理器的请求将被阻塞住，那么该处理器就可以独享共享内存。</p><h3 id="使用缓存锁保证原子性"><a href="#使用缓存锁保证原子性" class="headerlink" title="使用缓存锁保证原子性"></a>使用缓存锁保证原子性</h3><p>使用总线锁的时候把CPU和内存之间的通信锁住了，这使得锁定期间，其他处理器不能操作其他内存地址的数据，所以总线锁定的开销比较大，目前处理器在某些场合下使用缓存锁定代替总线锁定来进行优化。</p><p>频繁使用的内存会缓存在处理器的L1，L2，L3告诉缓存里，那么原子操作就可以直接在处理器内部缓存中进行，并不需要声明总线锁，现代处理器中就可以使用“缓存锁定”方式来实现复杂的原子性，那么当他执行锁操作回写到内存时，处理器不在总线上声明LOCK#信号，而是修改内部的内存地址，并允许它的缓存一致性机制来保证操作的原子性，因为缓存一致性机制会同时修改由两个以上处理器缓存的内存区域数据，当其他处理器回写已被锁定的缓存行的数据时，会使缓存行无效。</p><p>但是两种情况下处理器不会使用缓存锁定</p><ul><li>操作的数据不能被缓存在处理器内部，或操作的山上跨多个缓存行时，则处理器会调用总线锁定。</li><li>有些处理器不支持缓存锁定。</li></ul><h2 id="Java如何实现原子操作"><a href="#Java如何实现原子操作" class="headerlink" title="Java如何实现原子操作"></a>Java如何实现原子操作</h2><p>java中可以通过锁和循环CAS的方式来实现原子操作。</p><h3 id="使用循环CAS实现原子操作"><a href="#使用循环CAS实现原子操作" class="headerlink" title="使用循环CAS实现原子操作"></a>使用循环CAS实现原子操作</h3><p>JVM中的CAS操作就是利用了处理器提供的CMPXCHG指令实现的。自旋CAS实现的基本思路就是循环进行CAS操作知道成功为止。</p><p>下面是一个基于CAS线程安全的计数器方法safeCount和一个非线程安全的计数器count。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><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">public</span> <span class="keyword">class</span> <span class="title class_">Counter</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">AtomicInteger</span> <span class="variable">atomicI</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>(<span class="number">0</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> &#123;</span><br><span class="line">        <span class="keyword">final</span> <span class="type">Counter</span> <span class="variable">cas</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Counter</span>();</span><br><span class="line">        List&lt;Thread&gt; ts = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;(<span class="number">600</span>);</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 class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">0</span>; j &lt; <span class="number">100</span>; j++) &#123;</span><br><span class="line">            <span class="type">Thread</span> <span class="variable">t</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">10000</span>; i++) &#123;</span><br><span class="line">cas.count();</span><br><span class="line">                    cas.safeCount();</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;);</span><br><span class="line">            ts.add(t);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">for</span>(Thread t: ts) &#123;</span><br><span class="line">            t.start();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">for</span>(Thread t: ts) &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                t.join();</span><br><span class="line">            &#125; cache (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">        System.out.println(cas.i);</span><br><span class="line">        System.out.println(cas.atomicI.get());</span><br><span class="line">        System.out.println(System.currentTimeMillis() - start);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">safeCount</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">for</span>(;;) &#123;</span><br><span class="line">            <span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> atomicI.get();</span><br><span class="line">            <span class="type">boolean</span> <span class="variable">suc</span> <span class="operator">=</span> atomicI.compareAndSet(i, ++i);</span><br><span class="line">            <span class="keyword">if</span> (suc) <span class="keyword">break</span>;</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="keyword">private</span> <span class="keyword">void</span> <span class="title function_">count</span><span class="params">()</span> &#123;</span><br><span class="line">        i++;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>从Java5开始，jdk的并发包里提供了原子类，包括以原子的方式将当前值自增1和自减1</p></blockquote><h3 id="CAS实现原子操作的问题"><a href="#CAS实现原子操作的问题" class="headerlink" title="CAS实现原子操作的问题"></a>CAS实现原子操作的问题</h3><h4 id="ABA问题"><a href="#ABA问题" class="headerlink" title="ABA问题"></a>ABA问题</h4><p>因为CAS需要在操作值的时候，检查值有没有发生变化，如果没有发生变化则更新，但是如果一个值原来是A，变成了B，又变成了A，那么使用CAS进行检查时会发现它的值没有发生变化，但是实际上却变化了。</p><p>ABA问题的解决思路就是使用版本号。在变量前面追加上版本号，每次变量更新的时候将版本号加1，那么A-&gt;B-&gt;A就会变成1A-&gt;2B-&gt;3A。从Java5开始，JDK的Atomic包里提供了一个类<code>AtomicStampedReference</code>来解决ABA问题。这个类的compareAndSet方法的作用是首先检查当前引用是否等于预期引用，并且检查当前标志是否等于预期标志，如果全部相等，则以原子方式将该引用和该标志的值设置为给定的新值。</p><h4 id="循环时间长开销大"><a href="#循环时间长开销大" class="headerlink" title="循环时间长开销大"></a>循环时间长开销大</h4><p>自旋CAS如果长时间不成功，会给CPU带来非常大的执行开销。如果JVM能支持处理器的pause指令，那么效率还有一定的提升。pause指令有两个作用：</p><ul><li>延迟流水线执行指令</li><li>避免在退出循环时因内存顺序冲突引起CPU流水线被清空</li></ul><h4 id="只能保证一个共享变量的原子操作"><a href="#只能保证一个共享变量的原子操作" class="headerlink" title="只能保证一个共享变量的原子操作"></a>只能保证一个共享变量的原子操作</h4><h3 id="使用锁机制实现原子操作"><a href="#使用锁机制实现原子操作" class="headerlink" title="使用锁机制实现原子操作"></a>使用锁机制实现原子操作</h3><p>锁机制保证了只有获得锁的线程能够操作锁定的内存区域。JVM内部实现了很多种锁机制，有偏向锁、轻量级锁和互斥锁。有意思的是除了偏向锁，JVM实现锁的方式都用了循环CAS，即当一个线程想进入同步块的时候就是用循环CAS的方式来获取锁，当它推出同步块的时候使用循环CAS释放锁。</p>]]>
    </content>
    <id>https://chaofan.xyz/posts/73db57c3/</id>
    <link href="https://chaofan.xyz/posts/73db57c3/"/>
    <published>2022-07-31T23:12:40.000Z</published>
    <summary>
      <![CDATA[<span id="more"></span>

<p>原子(atomic)指的是“不能被进一步分割的最小粒子”，原子操作指的是“不可被中断的一个或一系列操作”。在多处理器上实现原子操作就会变得很复杂。</p>
<h2 id="处理器如何实现原子操作"><a href="#处理器]]>
    </summary>
    <title>原子操作的实现原理</title>
    <updated>2026-08-09T09:24:32.640Z</updated>
  </entry>
  <entry>
    <author>
      <name>chaofan</name>
      <email>chaofanxy@outlook.com</email>
    </author>
    <category term="Java" scheme="https://chaofan.xyz/categories/Java/"/>
    <category term="Spring" scheme="https://chaofan.xyz/tags/Spring/"/>
    <category term="SpringBoot" scheme="https://chaofan.xyz/tags/SpringBoot/"/>
    <category term="依赖注入" scheme="https://chaofan.xyz/tags/%E4%BE%9D%E8%B5%96%E6%B3%A8%E5%85%A5/"/>
    <content>
      <![CDATA[<span id="more"></span><h1 id="1-概述"><a href="#1-概述" class="headerlink" title="1. 概述"></a>1. 概述</h1><p>@Resource、@Inject和@Autowired是在Java开发中都会经常用到的注解，这些注解为类提供了一种声明性的方式来解决依赖关系</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"><span class="meta">@Autowired</span> </span><br><span class="line">AnotherClass object;</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></pre></td><td class="code"><pre><span class="line"><span class="type">AnotherClass</span> <span class="variable">object</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AnotherClass</span>();</span><br></pre></td></tr></table></figure><p>其中两个来自java扩展包：<code>javax.annotation.Resource</code>和<code>javax.inject.Inject</code>。<code>@Autowired</code>注解属于<code>org.springframework.beans.factory.annotation</code>包。</p><p>这些注解都可以通过字段注入或者setter注入来解决依赖关系，下面会分别比较一下它们之间的区别以及执行路径。</p><h1 id="2-Resource"><a href="#2-Resource" class="headerlink" title="2. @Resource"></a>2. @Resource</h1><p><em>@Resource</em>注释是<a href="https://jcp.org/en/jsr/detail?id=250">JSR-250</a>注释集合的一部分，并与 Jakarta EE 一起打包。此注解具有以下执行路径，按优先级列出：</p><ol><li>按名称匹配</li><li>按类型匹配</li><li>按限定符匹配</li></ol><p>这些执行路径适用于 setter 和 field 注入。</p><h2 id="2-1-字段注入"><a href="#2-1-字段注入" class="headerlink" title="2.1 字段注入"></a>2.1 字段注入</h2><p><em>我们可以通过使用@Resource</em>注释来注释实例变量来通过字段注入来解决依赖关系。</p><h3 id="2-1-1-按名称匹配"><a href="#2-1-1-按名称匹配" class="headerlink" title="2.1.1 按名称匹配"></a>2.1.1 按名称匹配</h3><p>我们将使用以下测试来演示按名称匹配字段注入：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@RunWith(SpringJUnit4ClassRunner.class)</span></span><br><span class="line"><span class="meta">@ContextConfiguration(loader = AnnotationConfigContextLoader.class,</span></span><br><span class="line"><span class="meta">                      classes = ApplicationContextTestResourceNameType.class)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">FieldResourceInjectionIntegrationTest</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Resource(name=&quot;namedFile&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> File defaultFile;</span><br><span class="line"></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_">givenResourceAnnotation_WhenOnField_ThenDependencyValid</span><span class="params">()</span>&#123;</span><br><span class="line">        assertNotNull(defaultFile);</span><br><span class="line">        assertEquals(<span class="string">&quot;namedFile.txt&quot;</span>, defaultFile.getName());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>看一下代码。在<em>FieldResourceInjectionTest</em>测试中，在第 7 行，我们将 bean 名称作为属性值传递给*@Resource*注释来按名称解析依项：</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"><span class="meta">@Resource(name=&quot;namedFile&quot;)</span></span><br><span class="line"><span class="keyword">private</span> File defaultFile;</span><br></pre></td></tr></table></figure><p>此配置将使用按名称匹配执行路径解析依赖关系。我们必须在<em>ApplicationContextTestResourceNameType</em>应用程序上下文中定义 bean <code>namedFile </code>。</p><p>注意 bean id 和对应的引用属性值必须匹配：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ApplicationContextTestResourceNameType</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean(name=&quot;namedFile&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> File <span class="title function_">namedFile</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">File</span> <span class="variable">namedFile</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">File</span>(<span class="string">&quot;namedFile.txt&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span> namedFile;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果我们未能在应用程序上下文中定义 bean，会抛出<code>NoSuchBeanDefinitionException</code>。我们可以通过更改<code>ApplicationContextTestResourceNameType</code>应用程序上下文中传递给<code>@Bean</code>注解的属性值，或者更改<em>FieldResourceInjectionTest</em>测试中传递给*@Resource*注解的属性值来证明这一点。</p><h3 id="2-1-2-按类型匹配"><a href="#2-1-2-按类型匹配" class="headerlink" title="2.1.2. 按类型匹配"></a>2.1.2. 按类型匹配</h3><p>为了演示按类型匹配的执行路径，我们只删除<code>FieldResourceInjectionTest</code>测试第 7 行的属性值：</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"><span class="meta">@Resource</span></span><br><span class="line"><span class="keyword">private</span> File defaultFile;</span><br></pre></td></tr></table></figure><p>然后我们再次运行测试。</p><p>测试仍然会通过，因为如果*@Resource*注释没有接收到 bean 名称作为属性值，Spring 框架将继续进行下一级优先级，按类型匹配，以尝试解决依赖关系。</p><h3 id="2-1-3-按限定符匹配"><a href="#2-1-3-按限定符匹配" class="headerlink" title="2.1.3 按限定符匹配"></a>2.1.3 按限定符匹配</h3><p>为了演示 match-by-qualifier 执行路径，修改测试场景，以便在<em>ApplicationContextTestResourceQualifier</em>应用程序上下文中定义两个 bean：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ApplicationContextTestResourceQualifier</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean(name=&quot;defaultFile&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> File <span class="title function_">defaultFile</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">File</span> <span class="variable">defaultFile</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">File</span>(<span class="string">&quot;defaultFile.txt&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span> defaultFile;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean(name=&quot;namedFile&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> File <span class="title function_">namedFile</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">File</span> <span class="variable">namedFile</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">File</span>(<span class="string">&quot;namedFile.txt&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span> namedFile;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>我们使用<em>QualifierResourceInjectionTest</em>测试来演示逐个匹配的依赖关系解析。在这种情况下，需要将特定的 bean 依赖注入到每个引用的变量中：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><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="meta">@RunWith(SpringJUnit4ClassRunner.class)</span></span><br><span class="line"><span class="meta">@ContextConfiguration(loader=AnnotationConfigContextLoader.class,</span></span><br><span class="line"><span class="meta">                      classes=ApplicationContextTestResourceQualifier.class)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">QualifierResourceInjectionIntegrationTest</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Resource</span></span><br><span class="line">    <span class="keyword">private</span> File dependency1;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Resource</span></span><br><span class="line">    <span class="keyword">private</span> File dependency2;</span><br><span class="line"></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_">givenResourceAnnotation_WhenField_ThenDependency1Valid</span><span class="params">()</span>&#123;</span><br><span class="line">        assertNotNull(dependency1);</span><br><span class="line">        assertEquals(<span class="string">&quot;defaultFile.txt&quot;</span>, dependency1.getName());</span><br><span class="line">    &#125;</span><br><span class="line"></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_">givenResourceQualifier_WhenField_ThenDependency2Valid</span><span class="params">()</span>&#123;</span><br><span class="line">        assertNotNull(dependency2);</span><br><span class="line">        assertEquals(<span class="string">&quot;namedFile.txt&quot;</span>, dependency2.getName());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>当我们运行测试时，会抛出<code>NoUniqueBeanDefinitionException</code> 。这会发生，因为应用程序上下文将找到两个类型为<code>File</code>的 bean 定义，并且不知道哪个 bean 应该解决依赖关系。</p><p>要解决这个问题，我们需要参考<em>QualifierResourceInjectionTest</em>测试的第 7 行到第 10 行：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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">@Resource</span></span><br><span class="line"><span class="keyword">private</span> File dependency1;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Resource</span></span><br><span class="line"><span class="keyword">private</span> File dependency2;</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="meta">@Qualifier(&quot;defaultFile&quot;)</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@Qualifier(&quot;namedFile&quot;)</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Resource</span></span><br><span class="line"><span class="meta">@Qualifier(&quot;defaultFile&quot;)</span></span><br><span class="line"><span class="keyword">private</span> File dependency1;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Resource</span></span><br><span class="line"><span class="meta">@Qualifier(&quot;namedFile&quot;)</span></span><br><span class="line"><span class="keyword">private</span> File dependency2;</span><br></pre></td></tr></table></figure><p>再次运行测试时，通过。</p><p>测试表明，即使我们在应用程序上下文中定义了多个 bean，我们也可以使用*@Qualifier*注释通过允许我们将特定的依赖项注入到一个类中来消除任何混淆。</p><h2 id="2-2-Setter注入"><a href="#2-2-Setter注入" class="headerlink" title="2.2. Setter注入"></a>2.2. Setter注入</h2><p>在字段上注入依赖项时所采用的执行路径也适用于setter注入。</p><h3 id="2-2-1-按名称匹配"><a href="#2-2-1-按名称匹配" class="headerlink" title="2.2.1 按名称匹配"></a>2.2.1 按名称匹配</h3><p>唯一的区别是<code>MethodResourceInjectionTest</code>有一个 setter 方法：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><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="meta">@RunWith(SpringJUnit4ClassRunner.class)</span></span><br><span class="line"><span class="meta">@ContextConfiguration(loader=AnnotationConfigContextLoader.class,</span></span><br><span class="line"><span class="meta">                      classes=ApplicationContextTestResourceNameType.class)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MethodResourceInjectionIntegrationTest</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> File defaultFile;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Resource(name=&quot;namedFile&quot;)</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">setDefaultFile</span><span class="params">(File defaultFile)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.defaultFile = defaultFile;</span><br><span class="line">    &#125;</span><br><span class="line"></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_">givenResourceAnnotation_WhenSetter_ThenDependencyValid</span><span class="params">()</span>&#123;</span><br><span class="line">        assertNotNull(defaultFile);</span><br><span class="line">        assertEquals(<span class="string">&quot;namedFile.txt&quot;</span>, defaultFile.getName());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>我们通过注解引用变量的相应 setter 方法，通过 setter 注入来解决依赖关系。然后我们将bean依赖的名称作为属性值传递给*@Resource*注解：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><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">private</span> File defaultFile;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Resource(name=&quot;namedFile&quot;)</span></span><br><span class="line"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">setDefaultFile</span><span class="params">(File defaultFile)</span> &#123;</span><br><span class="line">    <span class="built_in">this</span>.defaultFile = defaultFile;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在本例中，我们将重用<code>namedFile</code> bean 依赖项。bean 名称和相应的属性值必须匹配。</p><p>运行测试，通过。</p><p>为了让我们验证 match-by-name 执行路径是否解决了依赖关系，我们需要将传递给*@Resource<em>注释的属性值更改为我们选择的值并再次运行测试。这一次，测试失败并出现</em>NoSuchBeanDefinitionException*。</p><h3 id="2-2-2-按类型匹配"><a href="#2-2-2-按类型匹配" class="headerlink" title="2.2.2 按类型匹配"></a>2.2.2 按类型匹配</h3><p>为了演示基于 setter、按类型匹配的执行，我们将使用<em>MethodByTypeResourceTest</em>测试：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@RunWith(SpringJUnit4ClassRunner.class)</span></span><br><span class="line"><span class="meta">@ContextConfiguration(loader=AnnotationConfigContextLoader.class,</span></span><br><span class="line"><span class="meta">                      classes=ApplicationContextTestResourceNameType.class)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MethodByTypeResourceIntegrationTest</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> File defaultFile;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Resource</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">setDefaultFile</span><span class="params">(File defaultFile)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.defaultFile = defaultFile;</span><br><span class="line">    &#125;</span><br><span class="line"></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_">givenResourceAnnotation_WhenSetter_ThenValidDependency</span><span class="params">()</span>&#123;</span><br><span class="line">        assertNotNull(defaultFile);</span><br><span class="line">        assertEquals(<span class="string">&quot;namedFile.txt&quot;</span>, defaultFile.getName());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>运行这个测试时，通过。</p><p>为了让我们验证按类型匹配的执行路径是否解决了<em>File</em>依赖关系，我们需要将<em>defaultFile</em>变量的类类型更改为另一个类类型，如<em>String</em>。然后我们可以再次执行<em>MethodByTypeResourceTest</em>测试，这次会抛出<em>NoSuchBeanDefinitionException 。</em></p><p>该异常验证是否确实使用了按类型匹配来解决<em>文件</em>依赖关系。NoSuchBeanDefinitionException<em>确认</em>引用变量名称不需要与 bean 名称匹配。相反，依赖解析取决于 bean 的类类型与引用变量的类类型匹配。</p><h3 id="2-2-3-按限定符匹配"><a href="#2-2-3-按限定符匹配" class="headerlink" title="2.2.3 按限定符匹配"></a>2.2.3 按限定符匹配</h3><p>我们使用<em>MethodByQualifierResourceTest</em>测试来演示 match-by-qualifier 执行路径：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@RunWith(SpringJUnit4ClassRunner.class)</span></span><br><span class="line"><span class="meta">@ContextConfiguration(loader=AnnotationConfigContextLoader.class,</span></span><br><span class="line"><span class="meta">                      classes=ApplicationContextTestResourceQualifier.class)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MethodByQualifierResourceIntegrationTest</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> File arbDependency;</span><br><span class="line">    <span class="keyword">private</span> File anotherArbDependency;</span><br><span class="line"></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_">givenResourceQualifier_WhenSetter_ThenValidDependencies</span><span class="params">()</span>&#123;</span><br><span class="line">        assertNotNull(arbDependency);</span><br><span class="line">        assertEquals(<span class="string">&quot;namedFile.txt&quot;</span>, arbDependency.getName());</span><br><span class="line">        assertNotNull(anotherArbDependency);</span><br><span class="line">        assertEquals(<span class="string">&quot;defaultFile.txt&quot;</span>, anotherArbDependency.getName());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Resource</span></span><br><span class="line">    <span class="meta">@Qualifier(&quot;namedFile&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setArbDependency</span><span class="params">(File arbDependency)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.arbDependency = arbDependency;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Resource</span></span><br><span class="line">    <span class="meta">@Qualifier(&quot;defaultFile&quot;)</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setAnotherArbDependency</span><span class="params">(File anotherArbDependency)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.anotherArbDependency = anotherArbDependency;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>我们的测试表明，即使我们在应用程序上下文中定义了特定类型的多个 bean 实现，我们也可以使用*@Qualifier<em>注释和</em>@Resource*注释来解决依赖关系。</p><p>类似于基于字段的依赖注入，如果我们在一个应用上下文中定义多个bean，我们必须使用 <em>@Qualifier</em> 注解来指定使用哪个bean来解析依赖，否则会抛出<em>NoUniqueBeanDefinitionException 。</em></p><h1 id="3-Inject"><a href="#3-Inject" class="headerlink" title="3. @Inject"></a>3. @Inject</h1><p>@Inject<em>注释</em>属于<a href="https://jcp.org/en/jsr/detail?id=330">JSR-330</a>注释集合。此注解具有以下执行路径，按优先级列出：</p><ol><li>按类型匹配</li><li>按限定符匹配</li><li>按名称匹配</li></ol><p>这些执行路径适用于 setter 和 field 注入。为了让我们访问*@Inject<em>注解，我们必须将</em>javax.inject*库声明为依赖项。</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>javax.inject<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>javax.inject<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>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></pre></td></tr></table></figure><h2 id="3-1-字段注入"><a href="#3-1-字段注入" class="headerlink" title="3.1 字段注入"></a>3.1 字段注入</h2><h3 id="3-1-1-按类型匹配"><a href="#3-1-1-按类型匹配" class="headerlink" title="3.1.1 按类型匹配"></a>3.1.1 按类型匹配</h3><p>我们将修改测试示例以使用另一种类型的依赖项，即<em>ArbitraryDependency</em>类。<em>ArbitraryDependency</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ArbitraryDependency</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">String</span> <span class="variable">label</span> <span class="operator">=</span> <span class="string">&quot;Arbitrary Dependency&quot;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">toString</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> label;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这是有错误的<em>FieldInjectTest</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@RunWith(SpringJUnit4ClassRunner.class)</span></span><br><span class="line"><span class="meta">@ContextConfiguration(loader=AnnotationConfigContextLoader.class,</span></span><br><span class="line"><span class="meta">                      classes=ApplicationContextTestInjectType.class)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">FieldInjectIntegrationTest</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Inject</span></span><br><span class="line">    <span class="keyword">private</span> ArbitraryDependency fieldInjectDependency;</span><br><span class="line"></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_">givenInjectAnnotation_WhenOnField_ThenValidDependency</span><span class="params">()</span>&#123;</span><br><span class="line">        assertNotNull(fieldInjectDependency);</span><br><span class="line">        assertEquals(<span class="string">&quot;Arbitrary Dependency&quot;</span>,fieldInjectDependency.toString());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>与*@Resource注解首先按名称解析依赖关系不同，@* <em>Inject</em>注解的默认行为是按类型解析依赖关系。</p><p>这意味着即使类引用变量名称与 bean 名称不同，依赖关系仍然会被解析，前提是 bean 是在应用程序上下文中定义的。请注意以下测试中引用变量名称的方式：</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"><span class="meta">@Inject</span></span><br><span class="line"><span class="keyword">private</span> ArbitraryDependency fieldInjectDependency;</span><br></pre></td></tr></table></figure><p>与应用程序上下文中配置的 bean 名称不同：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="keyword">public</span> ArbitraryDependency <span class="title function_">injectDependency</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">ArbitraryDependency</span> <span class="variable">injectDependency</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ArbitraryDependency</span>();</span><br><span class="line">    <span class="keyword">return</span> injectDependency;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>当我们执行测试时，我们能够解决依赖关系。</p><h3 id="3-1-2-按限定符匹配"><a href="#3-1-2-按限定符匹配" class="headerlink" title="3.1.2 按限定符匹配"></a>3.1.2 按限定符匹配</h3><p>如果一个特定的类类型有多个实现，并且某个类需要一个特定的 bean，该怎么办？让我们修改测试示例，使其需要另一个依赖项。</p><p>在此示例中，我们将<em>ArbitraryDependency</em>类（在按类型匹配示例中使用）进行子类化，以创建<em>AnotherArbitraryDependency</em>类：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AnotherArbitraryDependency</span> <span class="keyword">extends</span> <span class="title class_">ArbitraryDependency</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">String</span> <span class="variable">label</span> <span class="operator">=</span> <span class="string">&quot;Another Arbitrary Dependency&quot;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">toString</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> label;</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Inject</span></span><br><span class="line"><span class="keyword">private</span> ArbitraryDependency defaultDependency;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Inject</span></span><br><span class="line"><span class="keyword">private</span> ArbitraryDependency namedDependency;</span><br></pre></td></tr></table></figure><p>我们可以使用<em>FieldQualifierInjectTest</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@RunWith(SpringJUnit4ClassRunner.class)</span></span><br><span class="line"><span class="meta">@ContextConfiguration(loader=AnnotationConfigContextLoader.class,</span></span><br><span class="line"><span class="meta">                      classes=ApplicationContextTestInjectQualifier.class)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">FieldQualifierInjectIntegrationTest</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Inject</span></span><br><span class="line">    <span class="keyword">private</span> ArbitraryDependency defaultDependency;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Inject</span></span><br><span class="line">    <span class="keyword">private</span> ArbitraryDependency namedDependency;</span><br><span class="line"></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_">givenInjectQualifier_WhenOnField_ThenDefaultFileValid</span><span class="params">()</span>&#123;</span><br><span class="line">        assertNotNull(defaultDependency);</span><br><span class="line">        assertEquals(<span class="string">&quot;Arbitrary Dependency&quot;</span>,defaultDependency.toString());</span><br><span class="line">    &#125;</span><br><span class="line"></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_">givenInjectQualifier_WhenOnField_ThenNamedFileValid</span><span class="params">()</span>&#123;</span><br><span class="line">        assertNotNull(defaultDependency);</span><br><span class="line">        assertEquals(<span class="string">&quot;Another Arbitrary Dependency&quot;</span>,namedDependency.toString());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果我们在应用程序上下文中有特定类的多个实现，并且<em>FieldQualifierInjectTest</em>测试尝试以下面列出的方式注入依赖项，则会抛出<em>NoUniqueBeanDefinitionException</em></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Inject</span> </span><br><span class="line"><span class="keyword">private</span> ArbitraryDependency defaultDependency;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Inject</span> </span><br><span class="line"><span class="keyword">private</span> ArbitraryDependency namedDependency;</span><br></pre></td></tr></table></figure><p>抛出这个异常是 Spring 框架指出某个类有多个实现的方式，它对使用哪一个感到困惑。为了阐明混淆，我们可以转到<em>FieldQualifierInjectTest</em>测试的第 7 行和第 10 行：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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">@Inject</span></span><br><span class="line"><span class="keyword">private</span> ArbitraryDependency defaultDependency;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Inject</span></span><br><span class="line"><span class="keyword">private</span> ArbitraryDependency namedDependency;</span><br></pre></td></tr></table></figure><p>我们可以将所需的 bean 名称传递给*@Qualifier<em>注释，我们将其与</em>@Inject*注释一起使用。这就是代码块现在的样子：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><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="meta">@Inject</span></span><br><span class="line"><span class="meta">@Qualifier(&quot;defaultFile&quot;)</span></span><br><span class="line"><span class="keyword">private</span> ArbitraryDependency defaultDependency;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Inject</span></span><br><span class="line"><span class="meta">@Qualifier(&quot;namedFile&quot;)</span></span><br><span class="line"><span class="keyword">private</span> ArbitraryDependency namedDependency;</span><br></pre></td></tr></table></figure><p><em>@Qualifier</em>注解在接收 bean 名称时要求严格匹配。我们必须确保将 bean 名称正确传递给<em>Qualifier</em>，否则将抛出*NoUniqueBeanDefinitionException 。*如果我们再次运行测试，它应该会通过。</p><h3 id="3-1-3-按名称匹配"><a href="#3-1-3-按名称匹配" class="headerlink" title="3.1.3 按名称匹配"></a>3.1.3 按名称匹配</h3><p>用于演示按名称匹配的<em>FieldByNameInjectTest</em>测试类似于按类型匹配执行路径。唯一的区别是现在我们需要一个特定的 bean，而不是一个特定的类型。在此示例中，我们再次对<em>ArbitraryDependency</em>类进行子类化以生成<em>YetAnotherArbitraryDependency</em>类：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">YetAnotherArbitraryDependency</span> <span class="keyword">extends</span> <span class="title class_">ArbitraryDependency</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">String</span> <span class="variable">label</span> <span class="operator">=</span> <span class="string">&quot;Yet Another Arbitrary Dependency&quot;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">toString</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> label;</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><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="meta">@RunWith(SpringJUnit4ClassRunner.class)</span></span><br><span class="line"><span class="meta">@ContextConfiguration(loader=AnnotationConfigContextLoader.class,</span></span><br><span class="line"><span class="meta">                      classes=ApplicationContextTestInjectName.class)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">FieldByNameInjectIntegrationTest</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Inject</span></span><br><span class="line">    <span class="meta">@Named(&quot;yetAnotherFieldInjectDependency&quot;)</span></span><br><span class="line">    <span class="keyword">private</span> ArbitraryDependency yetAnotherFieldInjectDependency;</span><br><span class="line"></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_">givenInjectQualifier_WhenSetOnField_ThenDependencyValid</span><span class="params">()</span>&#123;</span><br><span class="line">        assertNotNull(yetAnotherFieldInjectDependency);</span><br><span class="line">        assertEquals(<span class="string">&quot;Yet Another Arbitrary Dependency&quot;</span>, yetAnotherFieldInjectDependency.toString());</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ApplicationContextTestInjectName</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> ArbitraryDependency <span class="title function_">yetAnotherFieldInjectDependency</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">ArbitraryDependency</span> <span class="variable">yetAnotherFieldInjectDependency</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">YetAnotherArbitraryDependency</span>();</span><br><span class="line">        <span class="keyword">return</span> yetAnotherFieldInjectDependency;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果我们运行测试，它将通过。</p><p>为了验证我们是否通过按名称匹配执行路径注入了依赖项，我们需要将传入*@Named<em>注解的值yetAnotherFieldInjectDependency更改为我们选择的另一个名称。当我们再次运行测试时，会抛出</em>NoSuchBeanDefinitionException* 。</p><h2 id="3-2-Setter注入"><a href="#3-2-Setter注入" class="headerlink" title="3.2 Setter注入"></a>3.2 Setter注入</h2><p><em>@Inject</em>注解的基于设置器的注入类似于用于基于*@Resource*设置器的注入的方法。不是把注解在变量上面，而是注解在相应的 setter 方法。基于 setter 的注入所遵循的执行路径和基于字段的依赖注入也相同。</p><h1 id="4-Autowired"><a href="#4-Autowired" class="headerlink" title="4. @Autowired"></a>4. @Autowired</h1><p><em>@Autowired</em>注解的行为类似于*@Inject<em>注解。唯一的区别是</em>@Autowired*注解是 Spring 框架的一部分。<em>此注解与@Inject</em>注解具有相同的执行路径，按优先顺序列出：</p><ol><li>按类型匹配</li><li>按限定符匹配</li><li>按名称匹配</li></ol><p>这些执行路径适用于 setter 和 field 注入。</p><h2 id="4-1-字段注入"><a href="#4-1-字段注入" class="headerlink" title="4.1 字段注入"></a>4.1 字段注入</h2><h3 id="4-1-1-按类型匹配"><a href="#4-1-1-按类型匹配" class="headerlink" title="4.1.1 按类型匹配"></a>4.1.1 按类型匹配</h3><p>用于演示*@Autowired<em>按类型匹配执行路径的测试示例将类似于用于演示</em>@Inject<em>按类型匹配执行路径的测试。我们使用以下</em>FieldAutowiredTest<em>测试来演示使用</em>@Autowired*注释的按类型匹配：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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">@RunWith(SpringJUnit4ClassRunner.class)</span></span><br><span class="line"><span class="meta">@ContextConfiguration(loader=AnnotationConfigContextLoader.class,</span></span><br><span class="line"><span class="meta">                      classes=ApplicationContextTestAutowiredType.class)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">FieldAutowiredIntegrationTest</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> ArbitraryDependency fieldDependency;</span><br><span class="line"></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_">givenAutowired_WhenSetOnField_ThenDependencyResolved</span><span class="params">()</span> &#123;</span><br><span class="line">        assertNotNull(fieldDependency);</span><br><span class="line">        assertEquals(<span class="string">&quot;Arbitrary Dependency&quot;</span>, fieldDependency.toString());</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ApplicationContextTestAutowiredType</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> ArbitraryDependency <span class="title function_">autowiredFieldDependency</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">ArbitraryDependency</span> <span class="variable">autowiredFieldDependency</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ArbitraryDependency</span>();</span><br><span class="line">        <span class="keyword">return</span> autowiredFieldDependency;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>我们使用此测试来证明按类型匹配优先于其他执行路径。<em>注意FieldAutowiredTest</em>测试第 8 行的引用变量名称：</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"><span class="meta">@Autowired</span></span><br><span class="line"><span class="keyword">private</span> ArbitraryDependency fieldDependency;</span><br></pre></td></tr></table></figure><p>这与应用程序上下文中的 bean 名称不同：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="keyword">public</span> ArbitraryDependency <span class="title function_">autowiredFieldDependency</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">ArbitraryDependency</span> <span class="variable">autowiredFieldDependency</span> <span class="operator">=</span></span><br><span class="line">      <span class="keyword">new</span> <span class="title class_">ArbitraryDependency</span>();</span><br><span class="line">    <span class="keyword">return</span> autowiredFieldDependency;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>当我们运行测试时，它应该会通过。</p><p>为了确认依赖确实是使用 match-by-type 执行路径解决的，我们需要更改<em>fieldDependency</em>引用变量的类型并再次运行测试。这一次，<em>FieldAutowiredTest</em>测试将失败，并引发<em>NoSuchBeanDefinitionException</em>。这验证了我们使用了按类型匹配来解决依赖关系。</p><h3 id="4-1-2-按限定符匹配"><a href="#4-1-2-按限定符匹配" class="headerlink" title="4.1.2 按限定符匹配"></a>4.1.2 按限定符匹配</h3><p>如果我们遇到在应用程序上下文中定义了多个 bean 实现的情况怎么办：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ApplicationContextTestAutowiredQualifier</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> ArbitraryDependency <span class="title function_">autowiredFieldDependency</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">ArbitraryDependency</span> <span class="variable">autowiredFieldDependency</span> <span class="operator">=</span></span><br><span class="line">          <span class="keyword">new</span> <span class="title class_">ArbitraryDependency</span>();</span><br><span class="line">        <span class="keyword">return</span> autowiredFieldDependency;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> ArbitraryDependency <span class="title function_">anotherAutowiredFieldDependency</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">ArbitraryDependency</span> <span class="variable">anotherAutowiredFieldDependency</span> <span class="operator">=</span></span><br><span class="line">          <span class="keyword">new</span> <span class="title class_">AnotherArbitraryDependency</span>();</span><br><span class="line">        <span class="keyword">return</span> anotherAutowiredFieldDependency;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果我们执行以下<em>FieldQualifierAutowiredTest</em>测试，则会抛出<em>NoUniqueBeanDefinitionException ：</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></pre></td><td class="code"><pre><span class="line"><span class="meta">@RunWith(SpringJUnit4ClassRunner.class)</span></span><br><span class="line"><span class="meta">@ContextConfiguration(loader=AnnotationConfigContextLoader.class,</span></span><br><span class="line"><span class="meta">                      classes=ApplicationContextTestAutowiredQualifier.class)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">FieldQualifierAutowiredIntegrationTest</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> ArbitraryDependency fieldDependency1;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> ArbitraryDependency fieldDependency2;</span><br><span class="line"></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_">givenAutowiredQualifier_WhenOnField_ThenDep1Valid</span><span class="params">()</span>&#123;</span><br><span class="line">        assertNotNull(fieldDependency1);</span><br><span class="line">        assertEquals(<span class="string">&quot;Arbitrary Dependency&quot;</span>, fieldDependency1.toString());</span><br><span class="line">    &#125;</span><br><span class="line"></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_">givenAutowiredQualifier_WhenOnField_ThenDep2Valid</span><span class="params">()</span>&#123;</span><br><span class="line">        assertNotNull(fieldDependency2);</span><br><span class="line">        assertEquals(<span class="string">&quot;Another Arbitrary Dependency&quot;</span>,fieldDependency2.toString());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>异常是由于应用程序上下文中定义的两个 bean 引起的歧义。Spring 框架不知道哪个 bean 依赖项应该自动装配到哪个引用变量。我们可以通过在<em>FieldQualifierAutowiredTest</em>测试的第 7 行和第 10 行添加*@Qualifier*注释来解决此问题：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><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">@Autowired</span></span><br><span class="line"><span class="keyword">private</span> FieldDependency fieldDependency1;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Autowired</span></span><br><span class="line"><span class="keyword">private</span> FieldDependency fieldDependency2;</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 class="meta">@Autowired</span></span><br><span class="line"><span class="meta">@Qualifier(&quot;autowiredFieldDependency&quot;)</span></span><br><span class="line"><span class="keyword">private</span> FieldDependency fieldDependency1;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Autowired</span></span><br><span class="line"><span class="meta">@Qualifier(&quot;anotherAutowiredFieldDependency&quot;)</span></span><br><span class="line"><span class="keyword">private</span> FieldDependency fieldDependency2;</span><br></pre></td></tr></table></figure><p>当我们再次运行测试时，它将会通过。</p><h3 id="4-1-3-按名称匹配"><a href="#4-1-3-按名称匹配" class="headerlink" title="4.1.3 按名称匹配"></a>4.1.3 按名称匹配</h3><p>我们使用相同的测试场景来演示使用*@Autowired*注释注入字段依赖项的按名称匹配执行路径。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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="meta">@Component(&quot;autowiredFieldDependency&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ArbitraryDependency</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">String</span> <span class="variable">label</span> <span class="operator">=</span> <span class="string">&quot;Arbitrary Dependency&quot;</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">toString</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> label;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>传递到*@Component<em>注释的属性值</em>autowiredFieldDependency<em>告诉 Spring框架</em>ArbitraryDependency<em>类是一个名为</em>autowiredFieldDependency<em>的组件。为了让</em>@Autowired<em>注解通过名称解析依赖，组件名称必须与</em>FieldAutowiredNameTest*测试中定义的字段名称相对应；请参考第7行：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><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">@RunWith(SpringJUnit4ClassRunner.class)</span></span><br><span class="line"><span class="meta">@ContextConfiguration(loader=AnnotationConfigContextLoader.class,</span></span><br><span class="line"><span class="meta">                      classes=ApplicationContextTestAutowiredName.class)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">FieldAutowiredNameIntegrationTest</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> ArbitraryDependency autowiredFieldDependency;</span><br><span class="line"></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_">givenAutowired_WhenSetOnField_ThenDependencyResolved</span><span class="params">()</span>&#123;</span><br><span class="line">        assertNotNull(autowiredFieldDependency);</span><br><span class="line">        assertEquals(<span class="string">&quot;Arbitrary Dependency&quot;</span>, autowiredFieldDependency.toString());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>当我们运行<em>FieldAutowiredNameTest</em>测试时，它将会通过。</p><p>但是我们怎么知道*@Autowired<em>注解确实调用了按名称匹配的执行路径呢？我们可以将引用变量</em>autowiredFieldDependency*的名称更改为我们选择的另一个名称，然后再次运行测试。</p><p>这一次，测试将失败并抛出<em>NoUniqueBeanDefinitionException</em>。</p><p>类似的检查是将*@Component<em>属性值</em>autowiredFieldDependency<em>更改为我们选择的另一个值并再次运行测试。NoUniqueBeanDefinitionException</em>也会*被抛出。</p><p>这个异常证明如果我们使用不正确的 bean 名称，将找不到有效的 bean。这就是我们知道调用了按名称匹配执行路径的方式。</p><h2 id="4-2-Setter注入"><a href="#4-2-Setter注入" class="headerlink" title="4.2 Setter注入"></a>4.2 Setter注入</h2><p><em>@Autowired</em>注解的基于设置器的注入类似于为基于*@Resource*的setter注入的方法。所遵循的执行路径也和基于字段的依赖注入相同。</p><h1 id="5-怎么使用"><a href="#5-怎么使用" class="headerlink" title="5. 怎么使用"></a>5. 怎么使用</h1><p>下面提出了应该使用哪种注解以及在什么情况下使用的问题。这些问题的答案取决于应用面临的设计场景，以及开发人员希望如何利用基于每个注释的默认执行路径的多态性。</p><h2 id="5-1-通过多态性在应用程序范围内使用单例"><a href="#5-1-通过多态性在应用程序范围内使用单例" class="headerlink" title="5.1 通过多态性在应用程序范围内使用单例"></a>5.1 通过多态性在应用程序范围内使用单例</h2><p>如果设计使得应用程序行为基于接口或抽象类的实现，并且这些行为在整个应用程序中使用，那么我们可以使用*@Inject<em>或</em>@Autowired*注解。</p><p>这种方法的好处是，当我们升级应用程序或应用补丁来修复错误时，可以更换实现，而对整体应用程序行为的影响最小。在这种情况下，默认执行路径是按类型匹配。</p><h2 id="5-2-通过多态进行细粒度的应用程序行为配置"><a href="#5-2-通过多态进行细粒度的应用程序行为配置" class="headerlink" title="5.2 通过多态进行细粒度的应用程序行为配置"></a>5.2 通过多态进行细粒度的应用程序行为配置</h2><p>如果设计使得应用程序具有复杂的行为，每个行为都基于不同的接口或者抽象类，并且每个实现的用法在应用程序中有所不同，那么我们可以使用*@Resource*注解。在这种情况下，默认执行路径是按名称匹配。</p><h2 id="5-3-依赖注入应该由-Jakarta-EE-平台单独处理"><a href="#5-3-依赖注入应该由-Jakarta-EE-平台单独处理" class="headerlink" title="5.3 依赖注入应该由 Jakarta EE 平台单独处理"></a>5.3 依赖注入应该由 Jakarta EE 平台单独处理</h2><p>如果使用 Jakarta EE 平台而不是 Spring 注入所有依赖项的设计要求，那么选择是在*@Resource<em>注释和</em>@Inject*注释之间进行选择。我们应该根据需要哪个默认执行路径来缩小两个注释之间的选择。</p><h2 id="5-4-依赖注入应该由-Spring-框架单独处理"><a href="#5-4-依赖注入应该由-Spring-框架单独处理" class="headerlink" title="5.4 依赖注入应该由 Spring 框架单独处理"></a>5.4 依赖注入应该由 Spring 框架单独处理</h2><p>如果要求所有依赖项都由 Spring 框架处理，则唯一的选择是*@Autowired*注释。</p><h2 id="5-5-讨论总结"><a href="#5-5-讨论总结" class="headerlink" title="5.5 讨论总结"></a>5.5 讨论总结</h2><p>下表总结了我们的讨论</p><table><thead><tr><th align="left">设想</th><th>@Resource</th><th>@Inject</th><th>@Autowired</th></tr></thead><tbody><tr><td align="left">通过多态性在应用程序范围内使用单例</td><td>✗</td><td>✔</td><td>✔</td></tr><tr><td align="left">通过多态进行细粒度的应用程序行为配置</td><td>✔</td><td>✗</td><td>✗</td></tr><tr><td align="left">依赖注入应该由 Jakarta EE 平台单独处理</td><td>✔</td><td>✔</td><td>✗</td></tr><tr><td align="left">依赖注入应该由 Spring Framework 单独处理</td><td>✗</td><td>✗</td><td>✔</td></tr></tbody></table><h1 id="6-结论"><a href="#6-结论" class="headerlink" title="6 结论"></a>6 结论</h1><p>在本文中，我们更深入地了解每个注释的行为。了解每个注释的行为方式将有助于更好的整体应用程序设计和维护。</p>]]>
    </content>
    <id>https://chaofan.xyz/posts/9dcf60f3/</id>
    <link href="https://chaofan.xyz/posts/9dcf60f3/"/>
    <published>2022-07-22T22:03:44.000Z</published>
    <summary>
      <![CDATA[<span id="more"></span>

<h1 id="1-概述"><a href="#1-概述" class="headerlink" title="1. 概述"></a>1. 概述</h1><p>@Resource、@Inject和@Autowired是在Java开]]>
    </summary>
    <title>Autowired, Resource和Inject区别</title>
    <updated>2026-08-09T09:24:32.640Z</updated>
  </entry>
  <entry>
    <author>
      <name>chaofan</name>
      <email>chaofanxy@outlook.com</email>
    </author>
    <category term="Java" scheme="https://chaofan.xyz/categories/Java/"/>
    <category term="多线程" scheme="https://chaofan.xyz/tags/%E5%A4%9A%E7%BA%BF%E7%A8%8B/"/>
    <category term="并发" scheme="https://chaofan.xyz/tags/%E5%B9%B6%E5%8F%91/"/>
    <content>
      <![CDATA[<span id="more"></span><h2 id="synchronized"><a href="#synchronized" class="headerlink" title="synchronized"></a>synchronized</h2><p>最常见的锁，可以非常快速的实现多线程的同步操作，只需要在需要同步的<strong>方法</strong>、<strong>对象</strong>、或<strong>代码块</strong>中加入该关键字，就能保住同一时刻最多只有一个线程执行</p><p>使用synchronized修饰的代码具有原子性和可见性，在需要进程同步的程序中使用的频率非常高，可以满足一般的进程同步要求</p><blockquote><p>Java实现的锁机制有很多种，并且有些锁机制性能也比synchronized高，但还是强烈推荐在多线程应用程序中使用该关键字，因为实现方便，后续工作由JVM来完成，可靠性高。只有在确定锁机制是当前多线程程序的性能瓶颈时，才考虑使用其他机制，如ReentrantLock等。</p></blockquote><h3 id="原理"><a href="#原理" class="headerlink" title="原理"></a>原理</h3><p>从语法上讲，Synchronized可以把任何一个非null对象作为”锁”，在HotSpot中，<strong>锁有个专门的名字：对象监视器（Object Monitor）</strong>。</p><p>锁的对象：</p><ol><li>当synchronized作用在实例方法时，监视器锁（monitor）便是对象实例（this）；</li><li>当synchronized作用在静态方法时，监视器锁（monitor）便是对象的Class实例，因为Class数据存在于永久代，因此静态方法锁相当于该类的一个全局锁；</li><li>当synchronized作用在某一个对象实例时，监视器锁（monitor）便是括号括起来的对象实例；</li></ol><p>数据同步需要依赖锁，那锁的同步又依赖谁？synchronized给出的答案是在软件层面依赖JVM</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"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">test</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">synchronized</span> (<span class="built_in">this</span>) &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;Hello World&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</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></pre></td><td class="code"><pre><span class="line">;省略</span><br><span class="line">monitorenter</span><br><span class="line">getstatic #7 &lt;java/lang/System.out : Ljava/io/PrintStream;&gt;</span><br><span class="line">ldc #13 &lt;Hello World&gt;</span><br><span class="line">invokevirtual #15 &lt;java/io/PrintStream.println : (Ljava/lang/String;)V&gt;</span><br><span class="line">aload_1</span><br><span class="line">monitorexit</span><br><span class="line">goto 22 (+8)</span><br><span class="line">astore_2</span><br><span class="line">aload_1</span><br><span class="line">monitorexit</span><br><span class="line">;省略</span><br></pre></td></tr></table></figure><ol><li><p>monitorenter：每个对象都是一个监视器锁（monitor）。当monitor被占用时就会处于锁定状态，线程执行monitorenter指令时尝试获取monitor的所有权，过程如下：</p><blockquote><ol><li>如果monitor的进入数为0，则该线程进入monitor，然后将进入数设置为1，该线程即为monitor的所有者；</li><li>如果线程已经占有该monitor，只是重新进入，则进入monitor的进入数加1；</li><li>如果其他线程已经占用了monitor，则该线程进入阻塞状态，直到monitor的进入数为0，再重新尝试获取monitor的所有权；</li></ol></blockquote></li><li><p>monitorexit：执行monitorexit的线程必须是objectref所对应的monitor的所有者。指令执行时，monitor的进入数减1，如果减1后进入数为0，那线程退出monitor，不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去获取这个 monitor 的所有权。</p><blockquote><p>monitorexit指令出现了两次，第1次为同步正常退出释放锁；第2次为发生异步退出释放锁；</p></blockquote></li></ol><p>通过上面两段描述，我们应该能很清楚的看出Synchronized的实现原理，<strong>Synchronized的语义底层是通过一个<code>monitor</code>的对象来完成，其实wait&#x2F;notify等方法也依赖于<code>monitor</code>对象，这就是为什么只有在同步的块或者方法中才能调用wait&#x2F;notify等方法，否则会抛出<code>java.lang.IllegalMonitorStateException</code>的异常的原因。</strong></p><h2 id="ReentrantLock"><a href="#ReentrantLock" class="headerlink" title="ReentrantLock"></a>ReentrantLock</h2><p>可重入互斥锁，其基本行为和语义与使用同步方法和语句访问的隐式监视器锁相同，但具有扩展功能。</p><p><strong>ReentrantLock</strong>属于上次成功锁定但尚未解锁的线程。当锁不属于另一个线程时，调用锁的线程将返回并成功获取锁。如果当前线程已经拥有锁，则该方法将立即返回。</p><p>可以使用<code>isHeldByCurrentThread</code>和<code>getHoldCount</code>方法进行检查。 </p><p>此类的构造函数接受可选的公平性参数。如果设置为<code>true</code>，则在争用情况下，锁有利于授予对等待时间最长的线程的访问权。否则，此锁不保证任何特定的访问顺序。</p><p>与使用默认设置的程序相比，使用由多个线程访问的公平锁的程序可能会显示较低的总体吞吐量，但在获得锁和保证无饥饿的时间上差异较小。然而，请注意，锁的公平性并不能保证线程调度的公平性。因此，使用公平锁的多个线程中的一个线程可能会连续多次获得公平锁，而其他活动线程则没有进展，当前也没有持有该锁。还要注意，<code>tryLock()</code>方法不支持公平性设置。如果锁可用，即使其他线程正在等待，它也会成功。 </p><p>建议在调用后立即使用<code>try</code>块锁定，最典型的是在构建之前&#x2F;之后，例如： </p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">X</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">ReentrantLock</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">public</span> <span class="keyword">void</span> <span class="title function_">m</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="comment">// ... method body</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">&#125;</span><br></pre></td></tr></table></figure><p>需要额外注意的是：此类的序列化与内置锁的行为相同：反序列化的锁处于解锁状态，而不管其序列化时的状态如何。 </p><blockquote><p>最多支持同一线程的2147483647个递归锁。试图超过此限制会导致锁定方法引发错误。</p></blockquote><h2 id="Semaphore"><a href="#Semaphore" class="headerlink" title="Semaphore"></a>Semaphore</h2><p>计数型信号量。从概念上讲，信号量维护一组许可。如有必要，每个模块都会阻塞，直到获得许可证，然后再获取。</p><p>每次发布都会添加一个许可证，可能会释放一个阻塞收单机构。</p><p>但是，未使用实际许可对象；信号量只保留可用数字的计数，并相应地进行操作。 </p><p>信号量通常用于限制可以访问某些（物理或逻辑）资源的线程数。</p><p>例如，下面是一个类，它使用信号量控制对资源的访问：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><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">class</span> <span class="title class_">Pool</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">MAX_AVAILABLE</span> <span class="operator">=</span> <span class="number">100</span>;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="type">Semaphore</span> <span class="variable">available</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Semaphore</span>(MAX_AVAILABLE, <span class="literal">true</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> Object <span class="title function_">getItem</span><span class="params">()</span> <span class="keyword">throws</span> InterruptedException &#123;</span><br><span class="line">        available.acquire();</span><br><span class="line">        <span class="keyword">return</span> getNextAvailableItem();</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_">putItem</span><span class="params">(Object x)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (markAsUnused(x)) available.release();</span><br><span class="line">    &#125;      <span class="comment">// Not a particularly efficient data structure; just for demo      </span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">protected</span> Object[] items = ...; <span class="comment">// whatever kinds of items being managed    </span></span><br><span class="line">    <span class="keyword">protected</span> <span class="type">boolean</span>[] used = <span class="keyword">new</span> <span class="title class_">boolean</span>[MAX_AVAILABLE];</span><br><span class="line"></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">synchronized</span> Object <span class="title function_">getNextAvailableItem</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; MAX_AVAILABLE; ++i) &#123;</span><br><span class="line">            <span class="keyword">if</span> (!used[i]) &#123;</span><br><span class="line">                used[i] = <span class="literal">true</span>;</span><br><span class="line">                <span class="keyword">return</span> items[i];</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">null</span>; <span class="comment">// not reached    </span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">synchronized</span> <span class="type">boolean</span> <span class="title function_">markAsUnused</span><span class="params">(Object item)</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; MAX_AVAILABLE; ++i) &#123;</span><br><span class="line">            <span class="keyword">if</span> (item == items[i]) &#123;</span><br><span class="line">                <span class="keyword">if</span> (used[i]) &#123;</span><br><span class="line">                    used[i] = <span class="literal">false</span>;</span><br><span class="line">                    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">                &#125; <span class="keyword">else</span> <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">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在获取项目之前，每个线程必须从信号量获取许可证，以确保项目可用。</p><p>当线程处理完该项后，它将返回到池中，并向信号量返回一个许可证，允许另一个线程获取该项。</p><blockquote><p>调用acquire时不会保持同步锁，因为这会阻止项目返回到池中。</p></blockquote><p>信号量封装了限制对池的访问所需的同步，与维护池本身一致性所需的任何同步分开。 </p><p>一个初始化为1的信号量，其使用方式是它最多只有一个可用的许可证，可以用作互斥锁。这通常被称为二进制信号量，因为它只有两种状态：一个许可可用，或者零个许可可用。</p><p>以这种方式使用时，二进制信号量具有这样的属性（与许多<code>java.util.concurrent.locks.Lock</code>实现不同），即“锁”可以由所有者以外的线程释放（因为信号量没有所有权的概念）。</p><blockquote><p>这在某些特定的上下文中很有用，例如死锁恢复。 </p></blockquote><p>此类的构造函数可以选择接受公平性参数。当设置为false时，此类不保证线程获取许可的顺序。</p><p>特别是，允许<code>bargging</code>，也就是说，调用<code>acquire</code>的线程可以在等待的线程之前分配一个许可证：</p><p>从逻辑上讲，新线程将自己置于等待线程队列的头部。</p><p>当公平性设置为<code>true</code>时，信号量保证选择调用任何<code>acquire</code>方法的线程，以按照其调用这些方法的处理顺序（FIFO）获取许可。</p><blockquote><p>FIFO排序必然适用于这些方法中的特定内部执行点。</p></blockquote><p>因此，一个线程可以在另一个线程之前调用<code>acquire</code>，但可以在另一个线程之后到达排序点，类似地，从方法返回时也可以到达排序点。还要注意，<code>tryAcquire</code>方法不支持公平性设置，但将接受任何可用的许可。</p><p>通常，用于控制资源访问的信号量应该初始化为公平的，以确保没有线程因访问资源而耗尽。</p><p>当将信号量用于其他类型的同步控制时，非公平排序的吞吐量优势往往超过公平性考虑。 </p><p>此类还提供了方便的方法，可以一次获取和发布多个信号量。这些方法通常比循环更有效。</p><p>但是，它们不建立任何优先顺序。例如，如果线程A调用<code>s.acquire(3)</code>，线程B调用<code>s.acquire(2)</code>，并且2个信号量可用，那么不能保证线程B将获得它们，除非它的acquire先到达，并且信号量s处于公平模式。</p><blockquote><p>为避免线程因抛出异常而无法正常释放锁的情况发生，<strong>释放锁的操作必须在finally代码块中完成</strong></p></blockquote><h2 id="Atomic"><a href="#Atomic" class="headerlink" title="Atomic"></a>Atomic</h2><p>原子类，通过JUC提供的Atomic[Integer&#x2F;Boolean&#x2F;…]，提供了所有类型的原子操作</p><p>除了简单的<code>set</code>，<code>get</code>外，还包括<code>getAndSet</code>, <code>compareAndSet</code>, <code>getAndIncrement</code>等操作</p><p>通过<code>Unsafe</code>使这些方法都实现了原子性</p><h2 id="补充"><a href="#补充" class="headerlink" title="补充"></a>补充</h2><h3 id="公平锁、非公平锁"><a href="#公平锁、非公平锁" class="headerlink" title="公平锁、非公平锁"></a>公平锁、非公平锁</h3><p>公平锁指多个线程按照申请锁的顺序来获取锁，非公平锁就是没有顺序完全随机，所以能会造成优先级反转或者饥饿现象；<strong><code>synchronized </code>就是非公平锁，<code>ReentrantLock</code>（使用 CAS 和 AQS 实现） 通过构造参数可以决定是非公平锁还是公平锁，默认构造是非公平锁；非公平锁的吞吐量性能比公平锁大好。</strong></p><h3 id="可重入锁"><a href="#可重入锁" class="headerlink" title="可重入锁"></a>可重入锁</h3><p>又名递归锁，<strong>指在同一个线程在外层方法获取锁的时候在进入内层方法会自动获取锁，</strong><code>synchronized </code>和 <code>ReentrantLock </code>都是可重入锁，可重入锁可以在一定程度避免死锁。</p><h3 id="独享锁、共享锁"><a href="#独享锁、共享锁" class="headerlink" title="独享锁、共享锁"></a>独享锁、共享锁</h3><p>独享锁是指该锁一次只能被一个线程持有，共享锁指该锁可以被多个线程持有；<strong><code>synchronized </code>和 <code>ReentrantLock </code>都是独享锁，<code>ReadWriteLock </code>的读锁是共享锁，写锁是独占锁；</strong><code>ReentrantLock </code>的独享锁和共享锁也是通过 AQS 来实现的。</p><h3 id="互斥锁、读写锁"><a href="#互斥锁、读写锁" class="headerlink" title="互斥锁、读写锁"></a>互斥锁、读写锁</h3><p>其实就是独享锁、共享锁的具体说法；互斥锁实质就是 <code>ReentrantLock</code>，读写锁实质就是 <code>ReadWriteLock</code>。</p><h3 id="乐观锁、悲观锁"><a href="#乐观锁、悲观锁" class="headerlink" title="乐观锁、悲观锁"></a>乐观锁、悲观锁</h3><p>这个分类不是具体锁的分类，而是看待并发同步的角度；**悲观锁认为对于同一个数据的并发操作一定是会发生修改的（哪怕实质没修改也认为会修改），因此对于同一个数据的并发操作，悲观锁采取加锁的形式，因为悲观锁认为不加锁的操作一定有问题；乐观锁则认为对于同一个数据的并发操作是不会发生修改的，在更新数据的时候会采用不断的尝试更新，乐观锁认为不加锁的并发操作是没事的；**由此可以看出悲观锁适合写操作非常多的场景，乐观锁适合读操作非常多的场景，不加锁会带来大量的性能提升，悲观锁在 java 中很常见，乐观锁其实就是基于 CAS 的无锁编程，譬如 java 的原子类就是通过 CAS 自旋实现的。</p><h3 id="分段锁"><a href="#分段锁" class="headerlink" title="分段锁"></a>分段锁</h3><p>实质是一种锁的设计策略，不是具体的锁，对于 <code>ConcurrentHashMap </code>而言其并发的实现就是通过分段锁的形式来实现高效并发操作；当要 put 元素时并不是对整个 <code>hashmap </code>加锁，而是先通过 <code>hashcode </code>知道它要放在哪个分段，然后对分段进行加锁，所以多线程 put 元素时只要放在的不是同一个分段就做到了真正的并行插入，但是统计 size 时就需要获取所有的分段锁才能统计；分段锁的设计是为了细化锁的粒度。</p><h3 id="偏向锁、轻量级锁、重量级锁"><a href="#偏向锁、轻量级锁、重量级锁" class="headerlink" title="偏向锁、轻量级锁、重量级锁"></a>偏向锁、轻量级锁、重量级锁</h3><p>这种分类是按照锁状态来归纳的，并且是针对 <code>synchronized </code>的，java 1.6 为了减少获取锁和释放锁带来的性能问题而引入的一种状态，其状态会随着竞争情况逐渐升级，<strong>锁可以升级但不能降级，意味着偏向锁升级成轻量级锁后无法降为偏向锁，这种升级无法降级的策略目的就是为了提高获得锁和释放锁的效率。</strong></p><h3 id="自旋锁"><a href="#自旋锁" class="headerlink" title="自旋锁"></a>自旋锁</h3><p>其实是相对于互斥锁的概念，互斥锁线程会进入 <code>WAITING </code>状态和 <code>RUNNABLE </code>状态的切换，涉及上下文切换、cpu 抢占等开销，<strong>自旋锁的线程一直是 <code>RUNNABLE </code>状态的，一直在那循环检测锁标志位，机制不重复，但是自旋锁加锁全程消耗 cpu，起始开销虽然低于互斥锁，但随着持锁时间加锁开销是线性增长。</strong></p><h3 id="可中断锁"><a href="#可中断锁" class="headerlink" title="可中断锁"></a>可中断锁</h3><p><code>synchronized </code>是不可中断的，Lock 是可中断的，这里的可中断建立在阻塞等待中断，运行中是无法中断的。</p>]]>
    </content>
    <id>https://chaofan.xyz/posts/8dcf879b/</id>
    <link href="https://chaofan.xyz/posts/8dcf879b/"/>
    <published>2022-05-23T19:56:02.000Z</published>
    <summary>
      <![CDATA[<span id="more"></span>

<h2 id="synchronized"><a href="#synchronized" class="headerlink" title="synchronized"></a>synchronized</h2><p>最常见的锁]]>
    </summary>
    <title>Java中常用的锁</title>
    <updated>2026-08-09T09:24:32.640Z</updated>
  </entry>
  <entry>
    <author>
      <name>chaofan</name>
      <email>chaofanxy@outlook.com</email>
    </author>
    <category term="Java" scheme="https://chaofan.xyz/categories/Java/"/>
    <category term="unsafe" scheme="https://chaofan.xyz/tags/unsafe/"/>
    <content>
      <![CDATA[<p>众所周知，Java成为现在广泛使用的后端语言之一就是因为其拥有自动垃圾回收机制，由于垃圾回收的消耗，也导致Java运行效率不如没有垃圾回收机制的C++等，本文介绍Java中“不安全”的部分—手动申请和释放内存</p><span id="more"></span><h2 id="Unsafe类"><a href="#Unsafe类" class="headerlink" title="Unsafe类"></a>Unsafe类</h2><p>首先看一下Unsafe类注释</p><blockquote><p>A collection of methods for performing low-level, unsafe operations. Although the class and all methods are public, use of this class is limited because only trusted code can obtain instances of it. Note: It is the responsibility of the caller to make sure arguments are checked before methods of this class are called. While some rudimentary checks are performed on the input, the checks are best effort and when performance is an overriding priority, as when methods of this class are optimized by the runtime compiler, some or all checks (if any) may be elided. Hence, the caller must not rely on the checks and corresponding exceptions!</p><p>执行低级不安全操作的方法集合。尽管该类和所有方法都是公共的，但该类的使用受到限制，因为只有受信任的代码才能获得它的实例。注意：调用方有责任确保在调用此类的方法之前检查参数。虽然对输入执行了一些基本检查，但这些检查是尽最大努力的，当性能是压倒一切的优先级时，例如当此类的方法由运行时编译器优化时，可以省略一些或所有检查（如果有）。因此，调用方不能依赖于检查和相应的异常！</p></blockquote><p>可以看到两点：</p><ul><li>Unsafe类是低级不安全操作的方法</li><li>只有收到信任的代码才能获得实例</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> Unsafe <span class="title function_">getUnsafe</span><span class="params">()</span> &#123;</span><br><span class="line">    Class&lt;?&gt; caller = Reflection.getCallerClass();</span><br><span class="line">    <span class="keyword">if</span> (!VM.isSystemDomainLoader(caller.getClassLoader()))</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">SecurityException</span>(<span class="string">&quot;Unsafe&quot;</span>);</span><br><span class="line">    <span class="keyword">return</span> theUnsafe;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可以看到，在获取实例时会检查是否是Bootstrap类，只有Bootstrap才能获取实例进行不安全的操作，因此，一般我们有两种方法获取Unsafe的实例</p><h3 id="通过jvm参数设置引导类"><a href="#通过jvm参数设置引导类" class="headerlink" title="通过jvm参数设置引导类"></a>通过jvm参数设置引导类</h3><p>可以在启动时加上</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">-Xbootclasspath/p:path</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></pre></td><td class="code"><pre><span class="line">Unsafe.getUnsafe();</span><br></pre></td></tr></table></figure><p>获取Unsafe的实例，这种方法比较麻烦，因此接下来通过Java反射机制获取。</p><h3 id="通过反射获取"><a href="#通过反射获取" class="headerlink" title="通过反射获取"></a>通过反射获取</h3><p>我们观察到Unsafe类中维护了一个<code>theUnsafe</code>实例</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">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Unsafe</span> <span class="variable">theUnsafe</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Unsafe</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></pre></td><td class="code"><pre><span class="line"><span class="type">Field</span> <span class="variable">unsafeField</span> <span class="operator">=</span> Unsafe.class.getDeclaredField(<span class="string">&quot;theUnsafe&quot;</span>);</span><br><span class="line">unsafeField.setAccessible(<span class="literal">true</span>);</span><br><span class="line"><span class="type">Unsafe</span> <span class="variable">unsafe</span> <span class="operator">=</span> (Unsafe) unsafeField.get(<span class="literal">null</span>);</span><br></pre></td></tr></table></figure><p>拿到这个类的实例后我们能做什么呢？</p><p>可以观察一下这个类提供的方法</p><p>Unsafe API的大部分方法都是native实现，主要包括以下几类: </p><ol><li><p>Info相关。主要返回某些低级别的内存信息</p></li><li><p>Objects相关。主要提供Object和它的域操纵方法</p></li><li><p>Class相关。主要提供Class和它的静态域操纵方法</p></li><li><p>Arrays相关。数组操纵方法</p></li><li><p>Synchronization相关。 主要提供低级别同步原语（如基于CPU的CAS (Compare-And-Swap) 原语）</p></li><li><p>Memory相关。直接内存访问方法，绕过JVM堆直接操纵本地内存</p></li></ol><h2 id="应用场景"><a href="#应用场景" class="headerlink" title="应用场景"></a>应用场景</h2><h3 id="使String可变"><a href="#使String可变" class="headerlink" title="使String可变"></a>使String可变</h3><p><img src="https://file.chaofan.xyz/blog/202205211334236.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></pre></td><td class="code"><pre><span class="line"><span class="type">Field</span> <span class="variable">unsafeField</span> <span class="operator">=</span> Unsafe.class.getDeclaredField(<span class="string">&quot;theUnsafe&quot;</span>);</span><br><span class="line">unsafeField.setAccessible(<span class="literal">true</span>);</span><br><span class="line"><span class="type">Unsafe</span> <span class="variable">unsafe</span> <span class="operator">=</span> (Unsafe) unsafeField.get(<span class="literal">null</span>);</span><br><span class="line">Class&lt;String&gt; clazz = String.class;</span><br><span class="line"><span class="type">Field</span> <span class="variable">value</span> <span class="operator">=</span> clazz.getDeclaredField(<span class="string">&quot;value&quot;</span>);</span><br><span class="line"><span class="type">long</span> <span class="variable">offset</span> <span class="operator">=</span> unsafe.objectFieldOffset(value);</span><br><span class="line"></span><br><span class="line"><span class="keyword">final</span> <span class="type">String</span> <span class="variable">s</span> <span class="operator">=</span> <span class="string">&quot;123&quot;</span>;</span><br><span class="line">unsafe.putObject(s, offset, <span class="keyword">new</span> <span class="title class_">byte</span>[]&#123;<span class="string">&#x27;6&#x27;</span>, <span class="string">&#x27;6&#x27;</span>, <span class="string">&#x27;6&#x27;</span>&#125;);</span><br><span class="line">System.out.println(s);</span><br></pre></td></tr></table></figure><p>通过Unsafe类，我们可以修改被标记为final的对象</p><h3 id="手动申请和释放内存"><a href="#手动申请和释放内存" class="headerlink" title="手动申请和释放内存"></a>手动申请和释放内存</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">long</span> <span class="variable">address</span> <span class="operator">=</span> unsafe.allocateMemory(<span class="number">4</span> * Integer.SIZE);</span><br><span class="line">unsafe.putInt(address, <span class="number">12345</span>);</span><br><span class="line"><span class="type">int</span> <span class="variable">value</span> <span class="operator">=</span> unsafe.getInt(address);</span><br><span class="line">unsafe.freeMemory(address);</span><br><span class="line">System.out.println(value);</span><br></pre></td></tr></table></figure><p>其他的如内存拷贝，CAS操作就不一一演示了，基本概念和C++相似</p>]]>
    </content>
    <id>https://chaofan.xyz/posts/63b9b219/</id>
    <link href="https://chaofan.xyz/posts/63b9b219/"/>
    <published>2022-05-19T00:11:22.000Z</published>
    <summary>
      <![CDATA[<p>众所周知，Java成为现在广泛使用的后端语言之一就是因为其拥有自动垃圾回收机制，由于垃圾回收的消耗，也导致Java运行效率不如没有垃圾回收机制的C++等，本文介绍Java中“不安全”的部分—手动申请和释放内存</p>]]>
    </summary>
    <title>Java中的Unsafe类</title>
    <updated>2026-08-09T09:24:32.640Z</updated>
  </entry>
  <entry>
    <author>
      <name>chaofan</name>
      <email>chaofanxy@outlook.com</email>
    </author>
    <category term="操作系统" scheme="https://chaofan.xyz/categories/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/"/>
    <category term="Linux" scheme="https://chaofan.xyz/tags/Linux/"/>
    <category term="进程通信" scheme="https://chaofan.xyz/tags/%E8%BF%9B%E7%A8%8B%E9%80%9A%E4%BF%A1/"/>
    <category term="信号量" scheme="https://chaofan.xyz/tags/%E4%BF%A1%E5%8F%B7%E9%87%8F/"/>
    <content>
      <![CDATA[<p>信号量（Semaphore）的概念最早由荷兰计算机科学家 Dijkstra（迪杰斯特拉）提出，有时又称“信号灯”。</p><span id="more"></span><h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><ul><li><p>信号量本质上是一个非负数的计数器</p></li><li><p>给共享资源建立一个标志，表示该共享资源被占用情况</p></li></ul><p>进程对信号量有两个操作：</p><ul><li>P操作（申请资源）</li><li>V操作（释放资源）</li></ul><p>和互斥锁类似，信号量本质也是一个全局变量。不同之处在于，互斥锁的值只有 2 个（加锁 “lock” 和解锁 “unlock”），而信号量的值可以根据实际场景的需要自行设置（取值范围为 ≥0）。更重要的是，信号量还支持做“加 1”或者 “减 1”运算，且修改值的过程以“原子操作”的方式实现。</p><blockquote><p>原子操作是指当多个线程试图修改同一个信号量的值时，各线程修改值的过程不会互相干扰。例如信号量的初始值为 1，此时有 2 个线程试图对信号量做“加 1”操作，则信号量的值最终一定是 3，而不会是其它的值。反之若不以“原子操作”方式修改信号量的值，那么最终的计算结果还可能是 2（两个线程同时读取到的值为 1，各自在其基础上加 1，得到的结果即为 2）。</p></blockquote><p>多线程程序中，使用信号量需遵守以下几条规则：</p><ol><li>信号量的值不能小于 0；</li><li>有线程访问资源时，信号量执行“减 1”操作，访问完成后再执行“加 1”操作；</li><li>当信号量的值为 0 时，想访问资源的线程必须等待，直至信号量的值大于 0，等待的线程才能开始访问。</li></ol><p>根据初始值的不同，信号量可以细分为 2 类，分别为二进制信号量和计数信号量：</p><ul><li>二进制信号量：指初始值为 1 的信号量，此类信号量只有 1 和 0 两个值，通常用来替代互斥锁实现线程同步；</li><li>计数信号量：指初始值大于 1 的信号量，当进程中存在多个线程，但某公共资源允许同时访问的线程数量是有限的（出现了“狼多肉少”的情况），这时就可以用计数信号量来限制同时访问资源的线程数量。</li></ul><p>了解什么是信号量之后，接下来教大家如何创建并使用信号量。</p><h2 id="信号量的具体用法"><a href="#信号量的具体用法" class="headerlink" title="信号量的具体用法"></a>信号量的具体用法</h2><p>POSIX 标准中，信号量用 sem_t 类型的变量表示，该类型定义在<code>&lt;semaphore.h&gt;</code>头文件中。例如，下面代码定义了名为 mySem 的信号量：</p><figure class="highlight c"><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="meta">#<span class="keyword">include</span> <span class="string">&lt;semaphore.h&gt;</span></span></span><br><span class="line"><span class="type">sem_t</span> mySem;</span><br></pre></td></tr></table></figure><p>由此，我们就成功定义了一个 mySem 信号量。但要想使用它，还必须完成初始化操作。</p><h3 id="初始化信号量"><a href="#初始化信号量" class="headerlink" title="初始化信号量"></a>初始化信号量</h3><p>sem_init() 函数专门用来初始化信号量，语法格式如下：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">sem_init</span><span class="params">(<span class="type">sem_t</span> *sem, <span class="type">int</span> pshared, <span class="type">unsigned</span> <span class="type">int</span> value)</span>;</span><br></pre></td></tr></table></figure><p>各个参数的含义分别为：</p><ul><li>sem：表示要初始化的目标信号量；</li><li>pshared：表示该信号量是否可以和其他进程共享，pshared 值为 0 时表示不共享，值为 1 时表示共享；</li><li>value：设置信号量的初始值。</li></ul><p>当 sem_init() 成功完成初始化操作时，返回值为 0，否则返回 -1。</p><h3 id="操作信号量的函数"><a href="#操作信号量的函数" class="headerlink" title="操作信号量的函数"></a>操作信号量的函数</h3><p>对于初始化了的信号量，我们可以借助 &lt;semaphore.h&gt; 头文件提供的一些函数操作它，比如：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">sem_post</span><span class="params">(<span class="type">sem_t</span>* sem)</span>;</span><br><span class="line"><span class="type">int</span> <span class="title function_">sem_wait</span><span class="params">(<span class="type">sem_t</span>* sem)</span>;</span><br><span class="line"><span class="type">int</span> <span class="title function_">sem_trywait</span><span class="params">(<span class="type">sem_t</span>* sem)</span>;</span><br><span class="line"><span class="type">int</span> <span class="title function_">sem_destroy</span><span class="params">(<span class="type">sem_t</span>* sem)</span>; </span><br></pre></td></tr></table></figure><p>参数 sem 都表示要操作的目标信号量。各个函数的功能如下：</p><ul><li>sem_post() 函数的功能是：将信号量的值“加 1”，同时唤醒其它等待访问资源的线程；</li><li>当信号量的值大于 0 时，sem_wait() 函数会对信号量做“减 1”操作；当信号量的值为 0 时，sem_wait() 函数会阻塞当前线程，直至有线程执行 sem_post() 函数（使信号量的值大于 0），暂停的线程才会继续执行；</li><li>sem_trywait() 函数的功能和 sem_wait() 函数类似，唯一的不同在于，当信号量的值为 0 时，sem_trywait() 函数并不会阻塞当前线程，而是立即返回 -1；</li><li>sem_destory() 函数用于手动销毁信号量。</li></ul><p>以上函数执行成功时，返回值均为 0 ；如果执行失败，返回值均为 -1。</p><h2 id="信号量的实际应用"><a href="#信号量的实际应用" class="headerlink" title="信号量的实际应用"></a>信号量的实际应用</h2><p>前面讲过，信号量又细分为二进制信号量和计数信号量，虽然创建和使用它们的方法（函数）是相同的，但应用场景不同。</p><h3 id="二进制信号量"><a href="#二进制信号量" class="headerlink" title="二进制信号量"></a>二进制信号量</h3><p>二进制信号量常用于代替互斥锁解决线程同步问题，接下来我们使用二进制信号量模拟“4 个售票员卖 10 张票”的过程：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string">&lt;stdlib.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string">&lt;pthread.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string">&lt;semaphore.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string">&lt;unistd.h&gt;</span></span></span><br><span class="line"><span class="comment">//创建信号量</span></span><br><span class="line"><span class="type">sem_t</span> mySem;</span><br><span class="line"><span class="comment">//设置总票数</span></span><br><span class="line"><span class="type">int</span> ticket_sum = <span class="number">10</span>;</span><br><span class="line"><span class="comment">//模拟买票过程</span></span><br><span class="line"><span class="type">void</span> *<span class="title function_">sell_ticket</span><span class="params">(<span class="type">void</span> *arg)</span> &#123;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;当前线程ID：%u\n&quot;</span>, pthread_self());</span><br><span class="line">    <span class="type">int</span> i;</span><br><span class="line">    <span class="type">int</span> flag;</span><br><span class="line">    <span class="keyword">for</span> (i = <span class="number">0</span>; i &lt; <span class="number">10</span>; i++) &#123;</span><br><span class="line">        <span class="comment">//完成信号量&quot;减 1&quot;操作，否则暂停执行</span></span><br><span class="line">        flag = sem_wait(&amp;mySem);</span><br><span class="line">        <span class="keyword">if</span> (flag == <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="keyword">if</span> (ticket_sum &gt; <span class="number">0</span>) &#123;</span><br><span class="line">                sleep(<span class="number">1</span>);</span><br><span class="line">                <span class="built_in">printf</span>(<span class="string">&quot;%u 卖第 %d 张票\n&quot;</span>, pthread_self(), <span class="number">10</span> - ticket_sum + <span class="number">1</span>);</span><br><span class="line">                ticket_sum--;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">//执行“加1”操作</span></span><br><span class="line">            sem_post(&amp;mySem);</span><br><span class="line">            sleep(<span class="number">1</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="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">int</span> flag;</span><br><span class="line">    <span class="type">int</span> i;</span><br><span class="line">    <span class="type">void</span> *ans;</span><br><span class="line">    <span class="comment">//创建 4 个线程</span></span><br><span class="line">    <span class="type">pthread_t</span> tids[<span class="number">4</span>];</span><br><span class="line">    <span class="comment">//初始化信号量</span></span><br><span class="line">    flag = sem_init(&amp;mySem, <span class="number">0</span>, <span class="number">1</span>);</span><br><span class="line">    <span class="keyword">if</span> (flag != <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;初始化信号量失败\n&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">for</span> (i = <span class="number">0</span>; i &lt; <span class="number">4</span>; i++) &#123;</span><br><span class="line">        flag = pthread_create(&amp;tids[i], <span class="literal">NULL</span>, &amp;sell_ticket, <span class="literal">NULL</span>);</span><br><span class="line">        <span class="keyword">if</span> (flag != <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="built_in">printf</span>(<span class="string">&quot;线程创建失败!&quot;</span>);</span><br><span class="line">            <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    sleep(<span class="number">10</span>);</span><br><span class="line">    <span class="keyword">for</span> (i = <span class="number">0</span>; i &lt; <span class="number">4</span>; i++) &#123;</span><br><span class="line">        flag = pthread_join(tids[i], &amp;ans);</span><br><span class="line">        <span class="keyword">if</span> (flag != <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="built_in">printf</span>(<span class="string">&quot;tid=%d 等待失败！&quot;</span>, tids[i]);</span><br><span class="line">            <span class="keyword">return</span> <span class="number">0</span>;</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">    sem_destroy(&amp;mySem);</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>假设程序编写在 thread.c 文件中，执行过程如下：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><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">[root@localhost ~]# gcc thread.c -o thread.exe -lpthread</span><br><span class="line">[root@localhost ~]# ./thread.exe</span><br><span class="line">当前线程ID：1199965952</span><br><span class="line">当前线程ID：1189476096</span><br><span class="line">当前线程ID：1168496384</span><br><span class="line">当前线程ID：1178986240</span><br><span class="line">1199965952 卖第 1 张票</span><br><span class="line">1189476096 卖第 2 张票</span><br><span class="line">1199965952 卖第 3 张票</span><br><span class="line">1178986240 卖第 4 张票</span><br><span class="line">1168496384 卖第 5 张票</span><br><span class="line">1189476096 卖第 6 张票</span><br><span class="line">1199965952 卖第 7 张票</span><br><span class="line">1178986240 卖第 8 张票</span><br><span class="line">1168496384 卖第 9 张票</span><br><span class="line">1189476096 卖第 10 张票</span><br></pre></td></tr></table></figure><p>程序中信号量的初始值为 1，当有多个线程想执行 19~25 行代码时，第一个执行 sem_wait() 函数的线程可以继续执行，同时信号量的值会由 1 变为 0，其它线程只能等待信号量的值由 0 变为 1 后，才能继续执行。</p><h3 id="计数信号量"><a href="#计数信号量" class="headerlink" title="计数信号量"></a>计数信号量</h3><p>假设某银行只开设了 2 个窗口，但有 5 个人需要办理业务。如果我们使用多线程程序模拟办理业务的过程，可以借助计数信号量实现。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string">&lt;pthread.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string">&lt;stdlib.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string">&lt;semaphore.h&gt;</span></span></span><br><span class="line"><span class="comment">//设置办理业务的人数</span></span><br><span class="line"><span class="type">int</span> num = <span class="number">5</span>;</span><br><span class="line"><span class="comment">//创建信号量</span></span><br><span class="line"><span class="type">sem_t</span> sem;</span><br><span class="line"><span class="comment">//模拟办理业务的过程</span></span><br><span class="line"><span class="type">void</span> *<span class="title function_">get_service</span><span class="params">(<span class="type">void</span> *arg)</span>&#123;</span><br><span class="line">    <span class="type">int</span> id = *((<span class="type">int</span>*)arg);</span><br><span class="line">    <span class="comment">//信号量成功“减 1”后才能继续执行</span></span><br><span class="line">    <span class="keyword">if</span> (sem_wait(&amp;sem) == <span class="number">0</span>)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;---customer%d 正在办理业务\n&quot;</span>, id);</span><br><span class="line">        sleep(<span class="number">2</span>);</span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;---customer%d 已办完业务\n&quot;</span>, id);</span><br><span class="line">        <span class="comment">//信号量“加 1”</span></span><br><span class="line">        sem_post(&amp;sem);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">()</span>&#123;</span><br><span class="line">    <span class="type">int</span> flag,i,j;</span><br><span class="line">    <span class="comment">//创建 5 个线程代表 5 个人</span></span><br><span class="line">    <span class="type">pthread_t</span> customer[<span class="number">5</span>];</span><br><span class="line">    <span class="comment">//初始化信号量</span></span><br><span class="line">    sem_init(&amp;sem, <span class="number">0</span>, <span class="number">2</span>);</span><br><span class="line">    <span class="keyword">for</span> (i = <span class="number">0</span>; i &lt; num; i++) &#123;</span><br><span class="line">        flag = pthread_create(&amp;customer[i], <span class="literal">NULL</span>, get_service, &amp;i);</span><br><span class="line">        <span class="keyword">if</span> (flag != <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="built_in">printf</span>(<span class="string">&quot;线程创建失败!\n&quot;</span>);</span><br><span class="line">            <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="built_in">printf</span>(<span class="string">&quot;customer%d 来办理业务\n&quot;</span>,i);</span><br><span class="line">        &#125;</span><br><span class="line">        sleep(<span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">for</span> (j = <span class="number">0</span>; j &lt; num; j++) &#123;</span><br><span class="line">        flag = pthread_join(customer[j], <span class="literal">NULL</span>);</span><br><span class="line">        <span class="keyword">if</span> (flag != <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="built_in">printf</span>(<span class="string">&quot;tid=%d 等待失败！&quot;</span>, customer[i]);</span><br><span class="line">            <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    sem_destroy(&amp;sem);</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>假设程序编写在 thread.c 文件中，执行过程为：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><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">[root@localhost ~]# gcc thread.c -o thread.exe -lpthread</span><br><span class="line">[root@localhost ~]# ./thread.exe</span><br><span class="line">customer0 来办理业务</span><br><span class="line">---customer0 正在办理业务</span><br><span class="line">customer1 来办理业务</span><br><span class="line">---customer1 正在办理业务</span><br><span class="line">---customer0 已办完业务</span><br><span class="line">customer2 来办理业务</span><br><span class="line">---customer2 正在办理业务</span><br><span class="line">---customer1 已办完业务</span><br><span class="line">customer3 来办理业务</span><br><span class="line">---customer3 正在办理业务</span><br><span class="line">---customer2 已办完业务</span><br><span class="line">customer4 来办理业务</span><br><span class="line">---customer4 正在办理业务</span><br><span class="line">---customer3 已办完业务</span><br><span class="line">---customer4 已办完业务</span><br></pre></td></tr></table></figure><p>程序中，sem 信号量的初始化为 2，因此该信号量属于计数信号量。借助 sem 信号量，第 14~21 行的代码段最多只能有 2 个线程同时访问。</p>]]>
    </content>
    <id>https://chaofan.xyz/posts/c92c188e/</id>
    <link href="https://chaofan.xyz/posts/c92c188e/"/>
    <published>2022-05-03T18:27:33.000Z</published>
    <summary>
      <![CDATA[<p>信号量（Semaphore）的概念最早由荷兰计算机科学家 Dijkstra（迪杰斯特拉）提出，有时又称“信号灯”。</p>]]>
    </summary>
    <title>进程通信之信号量</title>
    <updated>2026-08-09T09:24:32.640Z</updated>
  </entry>
  <entry>
    <author>
      <name>chaofan</name>
      <email>chaofanxy@outlook.com</email>
    </author>
    <category term="操作系统" scheme="https://chaofan.xyz/categories/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/"/>
    <category term="Linux" scheme="https://chaofan.xyz/tags/Linux/"/>
    <category term="进程通信" scheme="https://chaofan.xyz/tags/%E8%BF%9B%E7%A8%8B%E9%80%9A%E4%BF%A1/"/>
    <category term="共享内存" scheme="https://chaofan.xyz/tags/%E5%85%B1%E4%BA%AB%E5%86%85%E5%AD%98/"/>
    <content>
      <![CDATA[<p>共享内存是一种用于实现进程间通信（IPC）的方法，不同进程通过访问同一块内存区域实现数据共享和交互。每个进程可以将自身的虚拟地址映射到物理内存中的特定区域，当不同进程将相同的物理内存区域与各自的虚拟地址空间关联时，这些进程就能实现通过共享内存来完成IPC。若某进程更改了共享内存区的内容，其它进程都会觉察到该区域的更改。</p><span id="more"></span><h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>共享内存是一种用于实现进程间通信（IPC）的方法，不同进程通过访问同一块内存区域实现数据共享和交互。每个进程可以将自身的虚拟地址映射到物理内存中的特定区域，当不同进程将相同的物理内存区域与各自的虚拟地址空间关联时，这些进程就能实现通过共享内存来完成IPC。若某进程更改了共享内存区的内容，其它进程都会觉察到该区域的更改。</p><p>每个进程有自己的进程控制块和地址空间，且都有一个与之对应的页表，负责将进程的虚拟地址与物理地址进行映射，通过内存管理单元（MMU）进行管理。两个不同的虚拟地址通过页表映射到物理空间的同一区域，它们所指向的这块区域即共享内存。</p><p>但是共享内存没有进程间同步与互斥机制。例如，进程A对共享内存执行写操作，在A的写入结束之前，进程B就可以从共享内存区读取数据，并无某种自动的机制来阻止进程B的读操作。一般为了实现进程同步和互斥，常常将共享内存和信号量配合使用。</p><h2 id="系统接口"><a href="#系统接口" class="headerlink" title="系统接口"></a>系统接口</h2><p>系统给出了共享内存相关的一些接口，常用的主要有创建共享内存、进程关联共享内存、进程去关联共享内存、设置共享内存等，各接口列举如下，其更详细的说明可参阅资料。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//创建</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string">&lt;sys/ipc.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string">&lt;sys/shm.h&gt;</span></span></span><br><span class="line"><span class="type">int</span> <span class="title function_">shmget</span><span class="params">(<span class="type">key_t</span> key, <span class="type">size_t</span> size, <span class="type">int</span> shmflg)</span>;</span><br></pre></td></tr></table></figure><p>该接口用于获得一个共享内存标识符或创建一个共享内存对象并返回其标识符。key为标识共享内存的键值，size以字节为单位设定内存大小，shmflg为内存模式标志参数。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//关联</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string">&lt;sys/types.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string">&lt;sys/shm.h&gt;</span></span></span><br><span class="line"><span class="type">void</span> *<span class="title function_">shmat</span><span class="params">(<span class="type">int</span> shmid, <span class="type">const</span> <span class="type">void</span> *shmaddr, <span class="type">int</span> shmflg)</span>;</span><br></pre></td></tr></table></figure><p>该接口将标识符为shmid的共享内存区域对象映射到调用进程的地址空间，shmaddr指定共享内存出现在进程内存地址所在的位置，常设为NULL让内核决定合适的位置。shmflg指定操作共享内存的方式。当映射成功时，函数返回附加好的共享内存地址。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//去关联</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string">&lt;sys/types.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string">&lt;sys/shm.h&gt;</span></span></span><br><span class="line"><span class="type">int</span> <span class="title function_">shmdt</span><span class="params">(<span class="type">const</span> <span class="type">void</span> *shmaddr)</span>;</span><br></pre></td></tr></table></figure><p>该接口用于断开共享内存连接，禁止本进程访问此片共享内存区域。shmaddr指连接的共享内存的起始地址，断开成功时函数返回0。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//设置(常用来删除)</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string">&lt;sys/types.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string">&lt;sys/shm.h&gt;</span></span></span><br><span class="line"><span class="type">int</span> <span class="title function_">shmctl</span><span class="params">(<span class="type">int</span> shmid, <span class="type">int</span> cmd, <span class="keyword">struct</span> shmid_ds *buf)</span>;</span><br></pre></td></tr></table></figure><p>该接口实现对共享内存段的控制，获取或设置其相关属性。cmd设置为<code>IPC_RMID</code>时该接口删除此段共享内存。</p><h2 id="例子"><a href="#例子" class="headerlink" title="例子"></a>例子</h2><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string">&lt;signal.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string">&lt;unistd.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string">&lt;sys/ipc.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string">&lt;sys/shm.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string">&lt;string.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">st_pid</span> &#123;</span></span><br><span class="line">    <span class="type">int</span> pid;</span><br><span class="line">    <span class="type">char</span> name[<span class="number">51</span>];</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">(<span class="type">int</span> argc, <span class="type">char</span> *argv[])</span> &#123;</span><br><span class="line">    <span class="type">int</span> shmid;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 申请共享内存</span></span><br><span class="line">    <span class="keyword">if</span> ((shmid = shmget(<span class="number">0x5005</span>, <span class="keyword">sizeof</span>(<span class="keyword">struct</span> st_pid), <span class="number">0640</span>|IPC_CREAT)) == <span class="number">-1</span>) &#123;</span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;shmget(ox5005) failed\n&quot;</span>);</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><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">st_pid</span> *<span class="title">stpid</span> =</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">if</span>((stpid = (<span class="keyword">struct</span> st_pid*)shmat(shmid, <span class="number">0</span>, <span class="number">0</span>)) == (<span class="type">void</span> *)<span class="number">-1</span>) &#123;</span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;shmat(ox5005) failed\n&quot;</span>);</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><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;pid=%d, name=%s\n&quot;</span>, stpid-&gt;pid, stpid-&gt;name);</span><br><span class="line">    stpid-&gt;pid = getpid();</span><br><span class="line">    <span class="built_in">strcpy</span>(stpid-&gt;name, argv[<span class="number">1</span>]);</span><br><span class="line"></span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;pid=%d, name=%s\n&quot;</span>, stpid-&gt;pid, stpid-&gt;name);</span><br><span class="line">    <span class="comment">// 分离共享内存</span></span><br><span class="line">    shmdt(stpid);</span><br><span class="line">    <span class="comment">// 删除共享内存</span></span><br><span class="line">    <span class="keyword">if</span>(shmctl(shmid, IPC_RMID, <span class="number">0</span>) == <span class="number">-1</span>) &#123;</span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;shmctl failed\n&quot;</span>);</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="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://chaofan.xyz/posts/c784b822/</id>
    <link href="https://chaofan.xyz/posts/c784b822/"/>
    <published>2022-05-02T19:24:40.000Z</published>
    <summary>
      <![CDATA[<p>共享内存是一种用于实现进程间通信（IPC）的方法，不同进程通过访问同一块内存区域实现数据共享和交互。每个进程可以将自身的虚拟地址映射到物理内存中的特定区域，当不同进程将相同的物理内存区域与各自的虚拟地址空间关联时，这些进程就能实现通过共享内存来完成IPC。若某进程更改了共享内存区的内容，其它进程都会觉察到该区域的更改。</p>]]>
    </summary>
    <title>进程通信之共享内存</title>
    <updated>2026-08-09T09:24:32.640Z</updated>
  </entry>
  <entry>
    <author>
      <name>chaofan</name>
      <email>chaofanxy@outlook.com</email>
    </author>
    <category term="操作系统" scheme="https://chaofan.xyz/categories/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/"/>
    <category term="Linux" scheme="https://chaofan.xyz/tags/Linux/"/>
    <category term="信号" scheme="https://chaofan.xyz/tags/%E4%BF%A1%E5%8F%B7/"/>
    <category term="进程通信" scheme="https://chaofan.xyz/tags/%E8%BF%9B%E7%A8%8B%E9%80%9A%E4%BF%A1/"/>
    <content>
      <![CDATA[<p>在 Linux 中，理解信号的概念是非常重要的。这是因为，信号被用于通过 Linux 命令行所做的一些常见活动中。例如，每当你按 Ctrl+C 组合键来从命令行终结一个命令的执行，你就使用了信号。知道信号的基本原理是非常有用的。</p><span id="more"></span><h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>在 Linux 系统（以及其他类 Unix操作系统）中，信号被用于进程间的通信。信号是一个发送到某个进程或同一进程中的特定线程的异步通知，用于通知发生的一个事件。从 1970 年贝尔实验室的 Unix 面世便有了信号的概念，而现在它已经被定义在了 POSIX 标准中。</p><p>对于在 Linux 环境进行编程的用户或系统管理员来说，较好地理解信号的概念和机制是很重要的，在某些情况下可以帮助我们更高效地编写程序。对于一个程序来说，如果每条指令都运行正常的话，它会连续地执行。但如果在程序执行时，出现了一个错误或任何异常，内核就可以使用信号来通知相应的进程。</p><p>信号同样被用于通信、同步进程和简化进程间通信，在 Linux 中，信号在处理异常和中断方面，扮演了极其重要的角色。信号巳经在没有任何较大修改的情况下被使用了将近 30 年。</p><p>当一个事件发生时，会产生一个信号，然后内核会将事件传递到接收的进程。有时，进程可以发送一个信号到其他进程。除了进程到进程的信号外，还有很多种情况，内核会产生一个信号，比如文件大小达到限额、一个 I&#x2F;O 设备就绪或用户发送了一个类似于 Ctrl+C 或 Ctrl+Z 的终端中断等。</p><p>运行在用户模式下的进程会接收信号。如果接收的进程正运行在内核模式，那么信号的执行只有在该进程返回到用户模式时才会开始。</p><p>发送到非运行进程的信号一定是由内核保存，直到进程重新执行为止。休眠的进程可以是可中断的，也可以是不可中断的。如果一个在可中断休眠状态的进程（例如，等待终端输入的进程）收到了一个信号，那么内核会唤醒这个进程来处理信号。如果一个在不可中断休眠状态的进程收到了一个信号，那么内核会拖延此信号，直到该事件完成为止。</p><p>当进程收到一个信号时，可能会发生以下 3 种情况：</p><ul><li><p>进程可能会忽略此信号。有些信号不能被忽略，而有些没有默认行为的信号，默认会被忽略。</p></li><li><p>进程可能会捕获此信号，并执行一个被称为信号处理器的特殊函数。</p></li><li><p>进程可能会执行信号的默认行为。例如，信号 15(SIGTERM) 的默认行为是结束进程。</p></li></ul><p>当一个进程执行信号处理时，如果还有其他信号到达，那么新的信号会被阻断直到处理器返冋为止。</p><h2 id="信号的名称和值"><a href="#信号的名称和值" class="headerlink" title="信号的名称和值"></a>信号的名称和值</h2><p>每个信号都有以<code>SIG</code>开头的名称，并定义为唯一的正整数。在 Shell 命令行提示符 下，输入<code>kill -l</code>命令，将显示所有信号的信号值和相应的信号名，类似如下所示：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">kill -l</span><br><span class="line">1) SIGHUP        2) SIGINT          3) SIGQUIT         4) SIGILL         5) SIGTRAP</span><br><span class="line">6) SIGABRT       7) SIGBUS          8) SIGFPE          9) SIGKILL       10) SIGUSR1</span><br><span class="line">11) SIGSEGV      12) SIGUSR2        13) SIGPIPE        14) SIGALRM       15) SIGTERM</span><br><span class="line">16) SIGSTKFLT    17) SIGCHLD        18) SIGCONT        19) SIGSTOP       20) SIGTSTP</span><br><span class="line">21) SIGTTIN      22) SIGTTOU        23) SIGURG         24) SIGXCPU       25) SIGXFSZ</span><br><span class="line">26) SIGVTALRM    27) SIGPROF        28) SIGWINCH       29) SIGIO         30) SIGPWR</span><br><span class="line">31) SIGSYS       34) SIGRTMIN       35) SIGRTMIN+1     36) SIGRTMIN+2    37) SIGRTMIN+3</span><br><span class="line">38) SIGRTMIN+4   39) SIGRTMIN+5     40) SIGRTMIN+6     41) SIGRTMIN+7    42) SIGRTMIN+8</span><br><span class="line">43) SIGRTMIN+9   44) SIGRTMIN+10    45) SIGRTMIN+11    46) SIGRTMIN+12   47) SIGRTMIN+13</span><br><span class="line">48) SIGRTMIN+14  49) SIGRTMIN+15    50) SIGRTMAX-14    51) SIGRTMAX-13   52) SIGRTMAX-12</span><br><span class="line">53) SIGRTMAX-11  54) SIGRTMAX-10    55) SIGRTMAX-9     56) SIGRTMAX-8    57) SIGRTMAX-7</span><br><span class="line">58) SIGRTMAX-6   59) SIGRTMAX-5     60) SIGRTMAX-4     61) SIGRTMAX-3    62) SIGRTMAX-2</span><br><span class="line">63) SIGRTMAX-1   64) SIGRTMAX</span><br></pre></td></tr></table></figure><p>信号值被定义在文件<code>/usr/include/bits/signum.h</code> 中，其源文件是 <code>/usr/src/linux/kernel/signal.c</code>。</p><p>在 Linux 下，可以查看 signal(7) 手册页来查阅信号名列表、信号值、默认的行为和它们是否可以被捕获。其命令如下所示：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">man 7 signal</span><br></pre></td></tr></table></figure><p>下标所列出的信号是 POSIX 标准的一部分，它们通常被缩写成不带<code>SIG</code>前缀，例如，SIGHUP 通常被简单地称为 HUP。</p><table><thead><tr><th>信 号</th><th>默认行为</th><th>描 述</th><th>信号值</th></tr></thead><tbody><tr><td>SIGABRT</td><td>生成 core 文件然后终止进程</td><td>这个信号告诉进程终止操作。ABRT 通常由进程本身发送，即当进程调用 abort() 函数发出一个非正常终止信号时</td><td>6</td></tr><tr><td>SIGALRM</td><td>终止</td><td>警告时钟</td><td>14</td></tr><tr><td>SIGBUS</td><td>生成 core 文件然后终止进程</td><td>当进程引起一个总线错误时，BUS 信号将被发送到进程。例如，访问了一部分未定义的内存对象</td><td>10</td></tr><tr><td>SIGCHLD</td><td>忽略</td><td>当了进程结束、被中断或是在被中断之后重新恢复时，CHLD 信号会被发送到进程</td><td>20</td></tr><tr><td>SIGCONT</td><td>继续进程</td><td>CONT 信号指不操作系统重新开始先前被 STOP 或 TSTP 暂停的进程</td><td>19</td></tr><tr><td>SIGFPE</td><td>生成 core 文件然后终止进程</td><td>当一个进程执行一个错误的算术运算时，FPE 信号会被发送到进程</td><td>8</td></tr><tr><td>SIGHUP</td><td>终止</td><td>当进程的控制终端关闭时，HUP 信号会被发送到进程</td><td>1</td></tr><tr><td>SIGILL</td><td>生成 core 文件然后终止进程</td><td>当一个进程尝试执行一个非法指令时，ILL 信号会被发送到进程</td><td>4</td></tr><tr><td>SIGINT</td><td>终止</td><td>当用户想要中断进程时，INT 信号被进程的控制终端发送到进程</td><td>2</td></tr><tr><td>SIGKILL</td><td>终止</td><td>发送到进程的 KILL 信号会使进程立即终止。KILL 信号不能被捕获或忽略</td><td>9</td></tr><tr><td>SIGPIPE</td><td>终止</td><td>当一个进程尝试向一个没有连接到其他目标的管道写入时，PIPE 信号会被发送到进程</td><td>13</td></tr><tr><td>SIGQUIT</td><td>终止</td><td>当用户要求进程执行 core dump 时，QUIT 信号由进程的控制终端发送到进程</td><td>3</td></tr><tr><td>SIGSEGV</td><td>生成 core 文件然后终止进程</td><td>当进程生成了一个无效的内存引用时，SEGV 信号会被发送到进程</td><td>11</td></tr><tr><td>SIGSTOP</td><td>停止进程</td><td>STOP 信号指示操作系统停止进程的执行</td><td>17</td></tr><tr><td>SIGTERM</td><td>终止</td><td>发送到进程的 TERM 信号用于要求进程终止</td><td>15</td></tr><tr><td>SIGTSTP</td><td>停止进程</td><td>TSTP 信号由进程的控制终端发送到进程来要求它立即终止</td><td>18</td></tr><tr><td>SIGTTIN</td><td>停止进程</td><td>后台进程尝试读取时，TTIN 信号会被发送到进程</td><td>21</td></tr><tr><td>SIGTTOU</td><td>停止进程</td><td>后台进程尝试输出时，TTOU 信号会被发送到进程</td><td>22</td></tr><tr><td>SIGUSR1</td><td>终止</td><td>发送到进程的 USR1 信号用于指示用户定义的条件</td><td>30</td></tr><tr><td>SIGUSR2</td><td>终止</td><td>同上</td><td>31</td></tr><tr><td>SIGPOLL</td><td>终止</td><td>当一个异步输入&#x2F;输出时间事件发生时，POLL  信号会被发送到进程</td><td>23</td></tr><tr><td>SIGPROF</td><td>终止</td><td>当仿形计时器过期时，PROF 信号会被发送到进程</td><td>27</td></tr><tr><td>SIGSYS</td><td>生成 core 文件然后终止进程</td><td>发生有错的系统调用时，SYS 信号会被发送到进程</td><td>12</td></tr><tr><td>SIGTRAP</td><td>生成 core 文件然后终止进程</td><td>追踪捕获&#x2F;断点捕获时，会产生 TRAP 信号。</td><td>5</td></tr><tr><td>SIGURG</td><td>忽略</td><td>当侖一个 <a href="http://c.biancheng.net/socket/">socket</a> 有紧急的或是带外数据可被读取时，URG 信号会被发送到进程</td><td>16</td></tr><tr><td>SIGVTALRM</td><td>终止</td><td>当进程使用的虚拟计时器过期时，VTALRM 信号会被发送到进程</td><td>26</td></tr><tr><td>SIGXCPU</td><td>终止</td><td>当进程使用的 CPU 时间超出限制时，XCPU 信号会被发送到进程</td><td>24</td></tr><tr><td>SIGXFSZ</td><td>生成 core 文件然后终止进程</td><td>当文件大小超过限制时，会产生 XFSZ 信号</td><td>25</td></tr></tbody></table><h2 id="信号的处理"><a href="#信号的处理" class="headerlink" title="信号的处理"></a>信号的处理</h2><p>信号的处理有三种办法、分别是：忽略、捕捉和默认动作，我们可以通过捕获程序接收到的信号，进行手动处理，而不是按照系统默认的方式来处理信号，因为系统大部分的默认处理方式都是“结束”程序，所以通过捕获信号，就能够自己编程进行信号处理。</p><ul><li><p>忽略信号，⼤多数信号可以使用这个方式来处理，但是有两种信号不能被忽略（分别是<code>SIGKILL</code>和<code>SIGSTOP</code>）。因为他们向内核和超级用户提供了进程终止和停止的可靠方法，如果忽略了，那么这个进程就变成了没⼈能管理的的进程，显然是内核设计者不希望看到的场景</p></li><li><p>捕捉信号，需要告诉内核，用户希望如何处理某⼀种信号，说⽩了就是写⼀个信号处理函数，然后将这个函数告诉内核。当该信号产生时，由内核来调用用户自定义的函数，以此来实现某种信号的处理。</p></li><li><p>系统默认动作，对于每个信号来说，系统都对应由默认的处理动作，当发生了该信号，系统会自动执行。不过，对系统来说，⼤部分的处理方式都比较粗暴，就是直接杀死该进程。</p></li></ul><p>具体的信号默认动作可以使用<code>man 7 signal</code>来查看系统的具体定义。</p><blockquote><p>《UNIX 环境⾼级编程（第三部）》的 P251——P256中间对于每个信号有详细的说明。 </p></blockquote><p>了解了信号之后，如何使用信号呢？</p><blockquote><p>最常用的kill命令就是一个发送信号的工具。比如，我在后台运行了⼀个 top ⼯具，通过ps 命令可以查看他的 PID，通过<code>kill 9 PID</code> 来发送了⼀个终止进程的信号来结束了 top 进程。如果查看信号编号和名称，可以发现9对应的是 9) SIGKILL，正是杀死该进程的信号。⽽以下的执行过程实际也就是执行了9号信号的默认动作——杀死进程。</p></blockquote><h3 id="信号处理函数的注册"><a href="#信号处理函数的注册" class="headerlink" title="信号处理函数的注册"></a>信号处理函数的注册</h3><p>信号注册函数不只⼀种方法，常用的是函数signal </p><p>信号发送函数也不止⼀个，常用的是kill 函数</p><h2 id="信号注册函数"><a href="#信号注册函数" class="headerlink" title="信号注册函数"></a>信号注册函数</h2><p>在正式开始了解这两个函数之前，可以先来思考⼀下，处理中断都需要处理什么问题。</p><p>按照我们之前思路来看，可以发送的信号类型是多种多样的，每种信号的处理可能不⼀定相同，那么，我们肯定需要知道到底发生了什么信号。</p><p>另外，虽然我们知道了系统发出来的是哪种信号，但是还有⼀点也很重要，就是系统产生了⼀个信号，是由谁来响应？</p><p>如果系统通过<kbd>Ctrl</kbd>+<kbd>C</kbd>产生了⼀个 SIGINT（中断信号），显然不是所有程序同时结束，那么，信号⼀定需要有⼀个接收者。对于处理信号的程序来说，接收者就是自己。</p><p>开始的时候，先来看看入门版本的信号注册函数，他的函数原型如下：</p><p>signal 的函数原型</p> <figure class="highlight c"><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="comment">//Defined in header &lt;signal.h&gt;</span></span><br><span class="line"><span class="type">void</span> (*signal( <span class="type">int</span> sig, <span class="type">void</span> (*handler) (<span class="type">int</span>))) (<span class="type">int</span>);</span><br></pre></td></tr></table></figure><p>根据函数原型可以看出由两部分组成，⼀个是真实处理信号的函数，另⼀个是注册函数了。</p><p>对于这个函数来说，sig 显然是信号的编号，handler 是中断函数的指针。</p><p>同样，中断函数的原型中，有⼀个参数是 int 类型，显然也是信号产生的类型，方便使用⼀个函数来处理多个信号。我们先来看看简单⼀个信号注册的代码示例吧。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string">&lt;signal.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string">&lt;unistd.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">void</span> <span class="title function_">handler</span><span class="params">(<span class="type">int</span> sig)</span> &#123;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;接收到了%d信号&quot;</span>, sig);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">for</span>(<span class="type">int</span> i = <span class="number">1</span>; i &lt;= <span class="number">64</span>; i++) &#123;</span><br><span class="line">        signal(i, handler);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">while</span>(<span class="number">1</span>) &#123;</span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;执行了一次\n&quot;</span>);</span><br><span class="line">        sleep(<span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用<kbd>Ctrl</kbd>+<kbd>C</kbd>和<kbd>Ctrl</kbd>+<kbd>Z</kbd>可以看到这两个常用的快捷键发送的是2信号和20信号，而且程序并没有停止，说明信号已经被成功捕捉到。</p><p><img src="https://file.chaofan.xyz/blog/202205012110130.png"></p><p>通过 kill 命令发送信号之前，我们需要先查看到接收者，通过 ps 命令查看了之前所写的程序的 PID，通过 kill 函数来发送。对于已注册的信号，使用 kill 发送都可以正常接收到，但是如果发送了未注册的信号，则会使得应用程序终止进程。</p><p>那么，已经可以设置信号处理函数了，信号的处理还有两种状态，分别是默认处理和忽略，这两种设置很简单，只需要将 handler 设置为</p><p>SIG_IGN（忽略信号）或 SIG_DFL（默认动作）即可。在此还有两个问题需要说明⼀下：</p><ol><li><p>当执行⼀个程序时，所有信号的状态都是系统默认或者忽略状态的。除非是 调用exec进程忽略了某些信号。exec 函数将原先设置为要捕捉的信号都更改为默认动作，其他信号的状态则不会改变 。</p></li><li><p>当⼀个进程调动了 fork 函数，那么子进程会继承父进程的信号处理方式。</p></li></ol><p>入门版的信号注册还是比较简单的，只需要⼀句注册和⼀个处理函数即可，那么，接下来看看，如何发送信号吧。</p><h2 id="信号发送函数"><a href="#信号发送函数" class="headerlink" title="信号发送函数"></a>信号发送函数</h2><p>kill 的函数原型</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;sys/types.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;signal.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">kill</span><span class="params">(<span class="type">pid_t</span> pid, <span class="type">int</span> sig)</span>;</span><br></pre></td></tr></table></figure><p>正如我之前所说的，信号的处理需要有接受者，显然发送者必须要知道发给谁，根据 kill 函数的远行可以看到，pid 就是接受者的 pid，sig 则是发送的信号的类型。从原型来看，发送信号要比接受信号还要简单些，那么我们直接上代码吧</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string">&lt;stdlib.h&gt;</span>                                                                                       #<span class="keyword">include</span><span class="string">&lt;signal.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span><span class="string">&lt;sys/types.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">(<span class="type">int</span> argc, <span class="type">char</span> *argv[])</span> &#123;</span><br><span class="line">    <span class="type">int</span> sig = atoi(argv[<span class="number">1</span>]);</span><br><span class="line">    <span class="type">int</span> pid = atoi(argv[<span class="number">2</span>]);</span><br><span class="line">    kill(sig, pid);</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>接收信号的结果</p><p><img src="https://file.chaofan.xyz/blog/202205012127584.png"></p><p>总结⼀下：</p><p>根据以上的结果可看到，基本可以实现了信号的发送，虽然不能直接发送信号名称，但是通过信号的编号，可以正常的给程序发送信号了，也是初步实现了信号的发送流程。</p><p>关于 kill 函数，还有⼀点需要额外说明，上⾯的程序限定了 pid 必须为⼤于0的正整数，其实 kill 函数传入的 pid 可以是小于等于0的整数。</p><ul><li><p>pid &gt; 0：将发送个该 pid 的进程</p></li><li><p>pid &#x3D;&#x3D; 0：将会把信号发送给与发送进程属于同⼀进程组的所有进程，并且发送进程具有权限想这些进程发送信号。pid &lt; 0：将信号发送给进程组ID 为 pid 的绝对值得，并且发送进程具有权限向其发送信号的所有进程</p></li><li><p>pid &#x3D;&#x3D; -1：将该信号发送给发送进程的有权限向他发送信号的所有进程。（不包括系统进程集中的进程）</p></li></ul><p>关于信号，还有更多的话题，比如，信号是否都能够准确的送达到⽬标进程呢？答案其实是不⼀定，那么这就有了可靠信号和不可靠信号</p>]]>
    </content>
    <id>https://chaofan.xyz/posts/99134621/</id>
    <link href="https://chaofan.xyz/posts/99134621/"/>
    <published>2022-05-01T20:16:21.000Z</published>
    <summary>
      <![CDATA[<p>在 Linux 中，理解信号的概念是非常重要的。这是因为，信号被用于通过 Linux 命令行所做的一些常见活动中。例如，每当你按 Ctrl+C 组合键来从命令行终结一个命令的执行，你就使用了信号。知道信号的基本原理是非常有用的。</p>]]>
    </summary>
    <title>进程通信之信号</title>
    <updated>2026-08-09T09:24:32.640Z</updated>
  </entry>
  <entry>
    <author>
      <name>chaofan</name>
      <email>chaofanxy@outlook.com</email>
    </author>
    <category term="应用程序" scheme="https://chaofan.xyz/categories/%E5%BA%94%E7%94%A8%E7%A8%8B%E5%BA%8F/"/>
    <category term="vim" scheme="https://chaofan.xyz/tags/vim/"/>
    <content>
      <![CDATA[<p>记录以下vim比较好用的配置，添加到<code>~/.vimrc</code>即可</p><span id="more"></span><figure class="highlight vim"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><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">setlocal</span> noswapfile <span class="comment">&quot; 不要生成swap文件</span></span><br><span class="line"><span class="keyword">set</span> bufhidden=<span class="keyword">hide</span> <span class="comment">&quot; 当buffer被丢弃的时候隐藏它</span></span><br><span class="line"><span class="keyword">set</span> nocompatible <span class="comment">&quot; 关闭 vi 兼容模式</span></span><br><span class="line"><span class="keyword">syntax</span> <span class="keyword">on</span> <span class="comment">&quot; 自动语法高亮</span></span><br><span class="line"><span class="keyword">colorscheme</span> evening <span class="comment">&quot; 设定配色方案</span></span><br><span class="line"><span class="keyword">set</span> <span class="keyword">number</span> <span class="comment">&quot; 显示行号</span></span><br><span class="line"><span class="keyword">set</span> cursorline <span class="comment">&quot; 突出显示当前行</span></span><br><span class="line"><span class="keyword">set</span> ruler <span class="comment">&quot; 打开状态栏标尺</span></span><br><span class="line"><span class="keyword">set</span> <span class="built_in">shiftwidth</span>=<span class="number">4</span> <span class="comment">&quot; 设定 &lt;&lt; 和 &gt;&gt; 命令移动时的宽度为 4</span></span><br><span class="line"><span class="keyword">set</span> softtabstop=<span class="number">4</span> <span class="comment">&quot; 使得按退格键时可以一次删掉 4 个空格</span></span><br><span class="line"><span class="keyword">set</span> tabstop=<span class="number">4</span> <span class="comment">&quot; 设定 tab 长度为 4</span></span><br><span class="line"><span class="keyword">set</span> nobackup <span class="comment">&quot; 覆盖文件时不备份</span></span><br><span class="line"><span class="keyword">set</span> autochdir <span class="comment">&quot; 自动切换当前目录为当前文件所在的目录</span></span><br><span class="line"><span class="keyword">filetype</span> plugin <span class="built_in">indent</span> <span class="keyword">on</span> <span class="comment">&quot; 开启插件</span></span><br><span class="line"><span class="keyword">set</span> backupcopy=yes <span class="comment">&quot; 设置备份时的行为为覆盖</span></span><br><span class="line"><span class="keyword">set</span> ignorecase smartcase <span class="comment">&quot; 搜索时忽略大小写，但在有一个或以上大写字母时仍保持对大小写敏感</span></span><br><span class="line"><span class="keyword">set</span> incsearch <span class="comment">&quot; 输入搜索内容时就显示搜索结果</span></span><br><span class="line"><span class="keyword">set</span> hlsearch <span class="comment">&quot; 搜索时高亮显示被找到的文本</span></span><br><span class="line"><span class="keyword">set</span> noerrorbells <span class="comment">&quot; 关闭错误信息响铃</span></span><br><span class="line"><span class="keyword">set</span> novisualbell <span class="comment">&quot; 关闭使用可视响铃代替呼叫</span></span><br><span class="line"><span class="keyword">set</span> t_vb= <span class="comment">&quot; 置空错误铃声的终端代码</span></span><br><span class="line"><span class="keyword">set</span> showmatch <span class="comment">&quot; 插入括号时，短暂地跳转到匹配的对应括号</span></span><br><span class="line"><span class="keyword">set</span> matchtime=<span class="number">2</span> <span class="comment">&quot; 短暂跳转到匹配括号的时间</span></span><br><span class="line"><span class="keyword">set</span> magic <span class="comment">&quot; 设置魔术</span></span><br><span class="line"><span class="keyword">set</span> hidden <span class="comment">&quot; 允许在有未保存的修改时切换缓冲区，此时的修改由 vim 负责保存</span></span><br><span class="line"><span class="keyword">set</span> smartindent <span class="comment">&quot; 开启新行时使用智能自动缩进</span></span><br><span class="line"><span class="keyword">set</span> backspace=<span class="built_in">indent</span>,eol,start <span class="comment">&quot; 不设定在插入状态无法用退格键和 Delete 键删除回车符</span></span><br><span class="line"><span class="keyword">set</span> cmdheight=<span class="number">1</span> <span class="comment">&quot; 设定命令行的行数为 1</span></span><br><span class="line"><span class="keyword">set</span> laststatus=<span class="number">2</span> <span class="comment">&quot; 显示状态栏 (默认值为 1, 无法显示状态栏)</span></span><br><span class="line"><span class="keyword">set</span> statusline=\ %&lt;%F[%<span class="number">1</span>*%M%*%n%R%H]%=\ %<span class="keyword">y</span>\ %<span class="number">0</span>(%&#123;&amp;fileformat&#125;\ %&#123;&amp;encoding&#125;\ Ln\ %<span class="keyword">l</span>,\ Col\ %<span class="keyword">c</span>/%L%) <span class="comment">&quot; 设置在状态行显示的信息</span></span><br><span class="line"><span class="keyword">set</span> foldenable <span class="comment">&quot; 开始折叠</span></span><br><span class="line"><span class="keyword">set</span> foldmethod=<span class="keyword">syntax</span> <span class="comment">&quot; 设置语法折叠</span></span><br><span class="line"><span class="keyword">set</span> foldcolumn=<span class="number">0</span> <span class="comment">&quot; 设置折叠区域的宽度</span></span><br><span class="line"><span class="keyword">setlocal</span> <span class="built_in">foldlevel</span>=<span class="number">1</span> <span class="comment">&quot; 设置折叠层数为 1</span></span><br><span class="line"><span class="keyword">nnoremap</span> <span class="symbol">&lt;space&gt;</span> @=((<span class="built_in">foldclosed</span>(<span class="built_in">line</span>(<span class="string">&#x27;.&#x27;</span>)) &lt; <span class="number">0</span>) ? <span class="string">&#x27;zc&#x27;</span> : <span class="string">&#x27;zo&#x27;</span>)<span class="symbol">&lt;CR&gt;</span> <span class="comment">&quot; 用空格键来开关折叠</span></span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://chaofan.xyz/posts/2a71af52/</id>
    <link href="https://chaofan.xyz/posts/2a71af52/"/>
    <published>2022-05-01T00:28:21.000Z</published>
    <summary>
      <![CDATA[<p>记录以下vim比较好用的配置，添加到<code>~/.vimrc</code>即可</p>]]>
    </summary>
    <title>vim常用配置</title>
    <updated>2026-08-09T09:24:32.640Z</updated>
  </entry>
  <entry>
    <author>
      <name>chaofan</name>
      <email>chaofanxy@outlook.com</email>
    </author>
    <category term="其他" scheme="https://chaofan.xyz/categories/%E5%85%B6%E4%BB%96/"/>
    <category term="Java" scheme="https://chaofan.xyz/tags/Java/"/>
    <category term="C++" scheme="https://chaofan.xyz/tags/C/"/>
    <content>
      <![CDATA[<p>比较效率和原理</p><span id="more"></span><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">a++：a先创建自身的一个副本，然后a自增1，最后返回副本的值</span><br><span class="line">a+=1: 事实上相当于++a</span><br><span class="line">a=a+1: 虽然有点雷同于a+=1，但不同的是此时右值的a和1做相加操作，形成一个副本然后赋值给a，所以有额外操作</span><br><span class="line">++a:将a自增1并返回a</span><br><span class="line">鉴于a++和++a的差别,C++Primer建议用++a作为for循环的递增量</span><br></pre></td></tr></table></figure><p> 效率问题：</p><p>1.在<strong>内建数据类型时</strong>（即自增表示式的结果没有被使用，只是简单的用于递增操作），这时这两个表达式的效率是相同的。</p><p>2.在<strong>自定义数据类型时</strong>（主要指有类的情况），由于++a可以返回对象的引用，<strong>而a++一定要是返回对象的值（因为局部对象不能返回引用）</strong>。可想而知引用的开销当然比直接对对象进行操作要效率高很多，节省很多开销。</p><p>前置++和后置++存在本质上的区别：<br>前置++ 不会产生临时对象<br>后置++ 在返回时有一个临时对象的创建<br>在前置++和后置++ 效果相同的时候，最好使用前置++</p>]]>
    </content>
    <id>https://chaofan.xyz/posts/1ee8fdbd/</id>
    <link href="https://chaofan.xyz/posts/1ee8fdbd/"/>
    <published>2022-04-27T23:59:18.000Z</published>
    <summary>
      <![CDATA[<p>比较效率和原理</p>]]>
    </summary>
    <title>a++、++a和+=区别</title>
    <updated>2026-08-09T09:24:32.640Z</updated>
  </entry>
  <entry>
    <author>
      <name>chaofan</name>
      <email>chaofanxy@outlook.com</email>
    </author>
    <category term="Java" scheme="https://chaofan.xyz/categories/Java/"/>
    <category term="垃圾收集" scheme="https://chaofan.xyz/tags/%E5%9E%83%E5%9C%BE%E6%94%B6%E9%9B%86/"/>
    <category term="内存" scheme="https://chaofan.xyz/tags/%E5%86%85%E5%AD%98/"/>
    <content>
      <![CDATA[<p>Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙，墙外面的人想进去，墙里面的人却想出来。</p><span id="more"></span><h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>垃圾收集（GC）并非Java所独创，1960年诞生于MIT的Lisp是第一个开始使用内存动态分配和垃圾收集技术的语言。其作者思考过垃圾收集需要完成的三件事情：</p><ul><li>哪些内存需要回收</li><li>什么时候回收</li><li>如何回收</li></ul><p>经过半个世纪的发展，现在内存动态分配和垃圾收集技术已经相当成熟，为什么还要去了解垃圾收集和内存分配？答案很简单：当需要排查各种内存溢出、内存泄漏问题时，当垃圾收集成为系统达到更高并发量的瓶颈时，就必须对这些”自动化“技术实施必要的监控和调节。</p><p>Java内存运行时区域分为</p><ul><li><p>程序计数器</p></li><li><p>Java虚拟机栈</p></li><li><p>本地方法栈</p></li><li><p>Java堆</p></li><li><p>方法区</p></li><li><p>运行时常量池</p></li><li><p>直接内存</p></li></ul><p>其中程序计数器、虚拟机栈和本地方法栈随着线程而生，随线程而灭，每一个栈帧中分配多少内存基本上是在类结构上就已知的，因此这几个区域的内存分配和回收都有确定性。</p><p>而Java堆和方法区有何很高的不确定性：一个接口的实现类需要的内存可能不一样，一个方法所执行的不同条件分支需要的内存也可能不一样，只有运行时才知道会创建哪些对象，创建多少对象，这部分的分配和回收是动态的。垃圾收集器所关注的就是这部分内存如何管理。</p><h2 id="垃圾收集算法"><a href="#垃圾收集算法" class="headerlink" title="垃圾收集算法"></a>垃圾收集算法</h2><p>堆里面存放着几乎所有对象实例，垃圾收集器在对堆进行回收前，第一件事情就是确定哪些对象”活着“，哪些已经”死去“。从这个角度出发，垃圾收集算法可以划分为<strong>引用计数式垃圾收集</strong>和<strong>追踪式垃圾收集</strong>两大类，这两类也被称为<em>直接垃圾收集</em>和<em>间接垃圾收集</em>，Java中所有垃圾回收算法均属于追踪式垃圾收集。</p><h3 id="分代收集理论"><a href="#分代收集理论" class="headerlink" title="分代收集理论"></a>分代收集理论</h3><p>当前商业虚拟机的垃圾收集器，大多数都遵循了<strong>分代收集</strong>的理论进行设计，一般把Java堆划分为<strong>新生代</strong>和<strong>老年代</strong>两个区域。在新生代，每次垃圾收集都有大批对象死去，每次回收后存活的少量对象，将会逐步晋升到老年代存放。</p><h3 id="标记-清除算法"><a href="#标记-清除算法" class="headerlink" title="标记-清除算法"></a>标记-清除算法</h3><p>最早出现也是最基础的垃圾收集算法时<strong>标记-清除</strong>算法。算法分为”标记“和”清除“两个阶段：首先标记处所有需要回收的对象，在标记完成后，统一回收掉所有被标记的对象，也可以反过来使用。</p><p>之所以说是最基础的收集算法，因为后续的收集算法大多都是以标记-清除算法为基础，对其缺点进行改造而得到的。</p><p>它的主要缺点有两个：</p><ul><li>执行效率不稳定</li><li>内存空间碎片化</li></ul><h3 id="标记-复制算法"><a href="#标记-复制算法" class="headerlink" title="标记-复制算法"></a>标记-复制算法</h3><p>为了解决标记-清除算法面对大量可回收对象执行效率地的问题，提出了称为”半区复制“的垃圾收集算法，它将可用内存按容量划分为大小相等的两块，每次只使用其中的一块。</p><p>当这一块的内存用完了，就将存活的对象复制到零另外一块上面，再把使用过的空间一次清理掉。如果内存中多数对象都是存活的，这种算法会产生大量的内存复制开销，但是对于多数对象都是可回收的情况，仅需要复制的就是占少数的存活对象，而且每次都是针对整个半区进行内存回收，也不用考虑空间碎片的复杂情况，只需要移动堆顶指针，按顺序分配即可。</p><p>这样实现简单高效，不过代价是将可用内存缩小为了原来的一半，空间浪费太多。</p><h3 id="标记-整理算法"><a href="#标记-整理算法" class="headerlink" title="标记-整理算法"></a>标记-整理算法</h3><p>标记-复制算法在对象存活率较高时需要进行较多的复制操作，效率会变低，更关键的是需要浪费一般的空间，所以老年代一般不能直接选用这种算法。</p><p>针对老年代对象的存亡特征，提出了标记-整理算法，其标记过程和标记-清除算法一样，但后续步骤不是直接对可回收对象进行清理，而是让所有存活的对象都向内存空间一端移动，然后直接清理掉边界以外的内存。</p><h2 id="经典垃圾收集器"><a href="#经典垃圾收集器" class="headerlink" title="经典垃圾收集器"></a>经典垃圾收集器</h2><h3 id="Serial收集器"><a href="#Serial收集器" class="headerlink" title="Serial收集器"></a>Serial收集器</h3><p>Serial收集器是最基础、历史最悠久的收集器，在JDK1.3.1之前是HotSpot虚拟机新生代收集器的唯一选择。顾名思义，这是一个单线程收集器，它的”单线程“不仅仅说明它只会使用一个处理器或者一条收集线程完成垃圾收集工作，更重要的是它在进行垃圾收集时，必须暂停其他所有工作线程，直到它收集结束，”Stop The World“。</p><p>事实上，迄今为止，Serial收集器依然是HotSpot运行在客户端模式下的默认新生代收集器，有着优于其他收集器的地方就是它简单而高效，对于内存资源受限的环境，它是所有收集器里额外消耗最小的；对于单核处理器或者处理器核心较少的环境来说，Serial收集器没有线程交互的开销，可以获得最高的单线程收集效率。对于一般几十兆到一两百兆的新生代来说，垃圾收集的停顿时间完全可以控制在十几、几十毫秒，最多一百毫秒以内，只要不是频繁发生收集，这点停顿时间对于许多用户来说完全可以接受。</p><h3 id="ParNew收集器"><a href="#ParNew收集器" class="headerlink" title="ParNew收集器"></a>ParNew收集器</h3><p>ParNew收集器实质上是Serial收集器的多线程并行版本，除了同时使用多条线程进行垃圾收集以外，其余的行为包括Serial收集器可用的所有控制参数、收集算法、Stop The World、对象分配规则，回收策略等都与Serial收集器完全一致，在实现上两种收集器也共用了相当多的代码。</p><h3 id="Parallel-Scavenge收集器"><a href="#Parallel-Scavenge收集器" class="headerlink" title="Parallel Scavenge收集器"></a>Parallel Scavenge收集器</h3><p>Parallel Scavenge收集器是一款新生代收集器，同样是基于标记-复制算法实现的收集器，也是能够并行收集的多线程收集器，基本上和ParNew非常相似。</p><p>Parallel Scavenge收集器的特点是它的关注点与其他收集器不同，CMS等收集器的关注点是尽可能缩短垃圾收集时用户线程的停顿时间，Parallel Scavenge收集器的目标则是达到一个可控制的吞吐量。也就是<strong>处理器用于运行用户代码的时间与处理器总消耗时间的比值</strong>。</p><h3 id="Serial-Old收集器"><a href="#Serial-Old收集器" class="headerlink" title="Serial Old收集器"></a>Serial Old收集器</h3><p>Serial Old收集器是Serial收集器的老年代版本，它同样是一个单线程收集器，使用标记-整理算法。这个收集器的主要意义是供客户端模式下的HotSpot使用。</p><h3 id="Parallel-Old收集器"><a href="#Parallel-Old收集器" class="headerlink" title="Parallel Old收集器"></a>Parallel Old收集器</h3><p>Parallel Old是Parallel Scavenge收集器的老年代版本，支持多线程并行收集，基于标记-整理算法实现。这个收集器直到JDK6才开始提供，在此之前，如果新生代选择了Parallel Scavenge收集器，老年代除了Serial Old收集器以外别无选择，其他表现良好的老年代收集器如CMS无法与他配合工作。</p><p>直到Parallel Old收集器出现后，<strong>吞吐量优先</strong>收集器终于有了比较名副其实的搭配组合，在注重吞吐量或者处理强资源较为稀缺的场合，都可以考虑Parallel Scavenge加Parallel Old收集器这个组合。</p><h3 id="CMS收集器"><a href="#CMS收集器" class="headerlink" title="CMS收集器"></a>CMS收集器</h3><p>CMS(Concurrent Mark Sweep) 收集器是一种获取最短回收停顿时间为目标的收集器，目前很大一部分的Java应用集中在互联网网站或者基于浏览器的B&#x2F;S系统的服务器上，这种应用通常较为关注服务的相应速度，系统系统缩短停顿时间，以给用户带来良好的交互体验。CMS收集器就非常符合这类应用的需求。</p><p>CMS收集器是基于标记-清除算法实现的，它的运行过程相对于前面几种收集器要更复杂一些，整个过程分为四步：</p><ul><li>初始标记（CMS initial mark）</li><li>并发标记（CMS concurrent mark）</li><li>重新标记（CMS remark）</li><li>并发清除（CMS concurrent sweep）</li></ul><p>其中初始标记、重新标记这两个步骤仍然需要”Stop The World”。初始标记仅仅只是标记一下GC Roots能直接关联到的对象，速度很快；并发标记阶段就是从GC Roots的直接关联对象开始遍历整个对象图的过程，这个过程消耗时间长但是不需要停顿用户线程，可以与垃圾收集线程一起并发运行；而重新标记阶段则是为了修正并发标记期间，因用户程序继续运作而产生变动的那一部分对象的标记记录，这个阶段的停顿时间通常比初始标记长一些，但也远远小于并发标记阶段是时间；最后是并发求清除阶段，清理删除掉标记阶段判断的已经死亡的对象，由于不需要移动存活对象，所以这个线程也可以与用户线程同时并发。</p><p>CMS是一款优秀的收集器，它最主要的优点在名字上已经体现出来：并发收集，低停顿。CMS是HotSpot追求第停顿的第一次成功尝试，但是它还远远达不到完美的程度，至少有以下三个明显的缺点：</p><ul><li>处理器资源非常敏感</li><li>无法处理浮动垃圾（Floating Garbage）</li><li>基于标记-清除算法导致会有大量空间碎片产生</li></ul><h3 id="Garbage-First收集器"><a href="#Garbage-First收集器" class="headerlink" title="Garbage First收集器"></a>Garbage First收集器</h3><p>G1收集器是垃圾收集器技术发展历史上的里程碑式的成果，它开创了收集器面向局部收集的设计思路和基于Region的内存布局形式。从JDK8 Update40之后，G1才被Oracle官方称为”全功能的垃圾收集器“（Fully-Featured Garbage Collector）。</p><p>G1是一款主要面向服务端应用的垃圾收集器。HotSpot团队最初赋予它的期望是未来可以替换掉JDK5中的CMD收集器。</p><p>G1收集器出现之前的所有其他收集器，垃圾收集的目标范围都是基于分代思想进行回收，而G1可以面向堆中任何部分来组成回收集进行回收，衡量标准不是它属于哪个分代，而是那块内存中皴法垃圾中最多，回收收益最大，这就是G1的mixed gc模式。</p>]]>
    </content>
    <id>https://chaofan.xyz/posts/aec894/</id>
    <link href="https://chaofan.xyz/posts/aec894/"/>
    <published>2022-04-27T14:59:36.000Z</published>
    <summary>
      <![CDATA[<p>Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙，墙外面的人想进去，墙里面的人却想出来。</p>]]>
    </summary>
    <title>Java垃圾收集器</title>
    <updated>2026-08-09T09:24:32.640Z</updated>
  </entry>
</feed>
