<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

  <title><![CDATA[沧海一粟No.1]]></title>
  <link href="https://nimitz871016.github.io/atom.xml" rel="self"/>
  <link href="https://nimitz871016.github.io/"/>
  <updated>2020-04-24T14:23:47+08:00</updated>
  <id>https://nimitz871016.github.io/</id>
  <author>
    <name><![CDATA[]]></name>
    
  </author>
  <generator uri="http://www.mweb.im/">MWeb</generator>
  
  <entry>
    <title type="html"><![CDATA[102. 二叉树的层次遍历]]></title>
    <link href="https://nimitz871016.github.io/15490058580119.html"/>
    <updated>2019-02-01T15:24:18+08:00</updated>
    <id>https://nimitz871016.github.io/15490058580119.html</id>
    <content type="html"><![CDATA[
<p><a href="https://leetcode-cn.com/problems/binary-tree-level-order-traversal/description/">https://leetcode-cn.com/problems/binary-tree-level-order-traversal/description/</a></p>

<h2 id="toc_0">题目</h2>

<p>给定一个二叉树，返回其按层次遍历的节点值。 （即逐层地，从左到右访问所有节点）。</p>

<p>例如:</p>

<pre><code class="language-text">给定二叉树:  [3,9,20,null,null,15,7],


    3
   / \
  9  20
    /  \
   15   7
   

返回其层次遍历结果：

[
  [3],
  [9,20],
  [15,7]
]
</code></pre>

<h2 id="toc_1">解题思路：</h2>

<p>层次遍历，没啥好说的，就是本题需要将层次区分开来。因此传统的方法，需要做一些修订。用一个队列保存当前层次的所有节点。</p>

<p>每一轮遍历的时候，依次出队所有的节点，存入结果，并遍历出队的所有节点，如果有子节点则存入队列，等待下一轮遍历。</p>

<p>也就是有两层的遍历，用一个临时的队列，保存中间结果</p>

<h2 id="toc_2">代码：</h2>

<pre><code class="language-text">public List&lt;List&lt;Integer&gt;&gt; levelOrder(TreeNode root) {
    Queue&lt;TreeNode&gt; queue = new ArrayDeque&lt;&gt;();
    if (root != null) {
        queue.add(root);
    }
    List&lt;List&lt;Integer&gt;&gt; result = new ArrayList&lt;&gt;();
    while(!queue.isEmpty()){
        List&lt;Integer&gt; ans = new ArrayList&lt;&gt;();
        Queue&lt;TreeNode&gt; tempQueue = new ArrayDeque&lt;&gt;(queue);
        while (!tempQueue.isEmpty()){
            TreeNode node = tempQueue.poll();
            ans.add(node.val);
        }
        tempQueue = new ArrayDeque&lt;&gt;(queue);
        queue.clear();
        while(!tempQueue.isEmpty()){
            TreeNode node = tempQueue.poll();
            if (node.left != null) {
                queue.add(node.left);
            }
            if (node.right != null) {
                queue.add(node.right);
            }
        }
        result.add(ans);
    }
    return result;
}
</code></pre>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[122. 买卖股票的最佳时机 II]]></title>
    <link href="https://nimitz871016.github.io/15490112223994.html"/>
    <updated>2019-02-01T16:53:42+08:00</updated>
    <id>https://nimitz871016.github.io/15490112223994.html</id>
    <content type="html"><![CDATA[
<p><a href="https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/description/">https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/description/</a></p>

<p>给定一个数组，它的第 i 个元素是一支给定股票第 i 天的价格。</p>

<p>设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易（多次买卖一支股票）。</p>

<p>注意：你不能同时参与多笔交易（你必须在再次购买前出售掉之前的股票）。</p>

<pre><code class="language-text">示例 1:

输入: [7,1,5,3,6,4]
输出: 7
解释: 在第 2 天（股票价格 = 1）的时候买入，在第 3 天（股票价格 = 5）的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
     随后，在第 4 天（股票价格 = 3）的时候买入，在第 5 天（股票价格 = 6）的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。
示例 2:

输入: [1,2,3,4,5]
输出: 4
解释: 在第 1 天（股票价格 = 1）的时候买入，在第 5 天 （股票价格 = 5）的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
     注意你不能在第 1 天和第 2 天接连购买股票，之后再将它们卖出。
     因为这样属于同时参与了多笔交易，你必须在再次购买前出售掉之前的股票。
示例 3:

输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。
</code></pre>

<h2 id="toc_0">解题思路：</h2>

<p>dp题。尝试写出状态转移方程。</p>

<p>设s[i]，第i天股票的价格</p>

<p>1) dp[i], 到第i天，最优的股票收益。</p>

<pre><code class="language-text">dp[i] = max {max{ s[i] - s[j] + dp[j - 1]} j = 1...i - 1,
</code></pre>

<p>dp[i - 1]代表不卖，以及s[i] &lt; s[j]的情况,算法复杂度O(n2)</p>

<h2 id="toc_1">代码：</h2>

<pre><code class="language-text">public int maxProfit(int[] prices) {
    if (prices.length == 0){
        return 0;
    }
    int[] dp = new int[prices.length];
    for (int i = 1 ; i &lt; prices.length; i++){
        int max = dp[i - 1];
        for (int j = 0; j &lt; i; j++){
            int temp = prices[i] - prices[j];
            temp = temp &lt; 0 ? 0 : temp;
            if (j &gt; 0) {
                if (temp &gt; 0) {
                    temp += dp[j - 1];
                } else {
                    temp = dp[j - 1];
                }
            }
            if (temp &gt; max){
                max = temp;
            }
        }
        dp[i] = max;
    }
    return dp[prices.length - 1];
}
</code></pre>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[1349. 参加考试的最大学生数]]></title>
    <link href="https://nimitz871016.github.io/15814116703606.html"/>
    <updated>2020-02-11T17:01:10+08:00</updated>
    <id>https://nimitz871016.github.io/15814116703606.html</id>
    <content type="html"><![CDATA[
<p>给你一个 <code>m * n</code> 的矩阵 seats 表示教室中的座位分布。如果座位是坏的（不可用），就用 &#39;#&#39; 表示；否则，用 &#39;.&#39; 表示。</p>

<p>学生可以看到左侧、右侧、左上、右上这四个方向上紧邻他的学生的答卷，但是看不到直接坐在他前面或者后面的学生的答卷。请你计算并返回该考场可以容纳的一起参加考试且无法作弊的最大学生人数。</p>

<p>学生必须坐在状况良好的座位上。<br/>
 </p>

<p>示例 1：</p>

<p><img src="media/15814116703606/15814117367670.jpg" alt="" style="width:361px;"/></p>

<pre><code class="language-text">输入：seats = [[&quot;#&quot;,&quot;.&quot;,&quot;#&quot;,&quot;#&quot;,&quot;.&quot;,&quot;#&quot;],
              [&quot;.&quot;,&quot;#&quot;,&quot;#&quot;,&quot;#&quot;,&quot;#&quot;,&quot;.&quot;],
              [&quot;#&quot;,&quot;.&quot;,&quot;#&quot;,&quot;#&quot;,&quot;.&quot;,&quot;#&quot;]]
输出：4
解释：教师可以让 4 个学生坐在可用的座位上，这样他们就无法在考试中作弊。 
</code></pre>

<p>示例 2：</p>

<pre><code class="language-text">输入：seats = [[&quot;.&quot;,&quot;#&quot;],
              [&quot;#&quot;,&quot;#&quot;],
              [&quot;#&quot;,&quot;.&quot;],
              [&quot;#&quot;,&quot;#&quot;],
              [&quot;.&quot;,&quot;#&quot;]]
输出：3
解释：让所有学生坐在可用的座位上。
</code></pre>

<p>示例 3：</p>

<pre><code class="language-text">输入：seats = [[&quot;#&quot;,&quot;.&quot;,&quot;.&quot;,&quot;.&quot;,&quot;#&quot;],
              [&quot;.&quot;,&quot;#&quot;,&quot;.&quot;,&quot;#&quot;,&quot;.&quot;],
              [&quot;.&quot;,&quot;.&quot;,&quot;#&quot;,&quot;.&quot;,&quot;.&quot;],
              [&quot;.&quot;,&quot;#&quot;,&quot;.&quot;,&quot;#&quot;,&quot;.&quot;],
              [&quot;#&quot;,&quot;.&quot;,&quot;.&quot;,&quot;.&quot;,&quot;#&quot;]]
输出：10
解释：让学生坐在第 1、3 和 5 列的可用座位上。
</code></pre>

<p>提示：</p>

<ul>
<li>seats 只包含字符 &#39;.&#39; 和&#39;#&#39;</li>
<li>m == seats.length</li>
<li>n == seats[i].length</li>
<li>1 &lt;= m &lt;= 8</li>
<li>1 &lt;= n &lt;= 8</li>
</ul>

<h2 id="toc_0">2. 结题思路</h2>

<h3 id="toc_1">2.1. 思路过程</h3>

<ol>
<li>当前行的状态只和前一行的状态有关。</li>
<li>为了表示前一行的状态，加上题目矩阵规模小于8，可以使用状态压缩。</li>
<li>一个简便方法，采用位运算快速进行移位，以及有效状态的判断。</li>
</ol>

<h3 id="toc_2">2.2. 细节考虑</h3>

<ol>
<li>位运算，通过左移，右移一位，可以快速判断，当前的状态是否合法。</li>
<li>将合法的座位，映射为0。坏掉的座位，映射为1，与当前状态与（&amp;），可以快速判断当前状态是否合法</li>
</ol>

<h2 id="toc_3">3. 代码</h2>

<pre><code class="language-text">public int maxStudents(char[][] seats) {
    int m = seats.length;
    int n = seats[0].length;
    int[][] dp = new int[m + 1][1 &lt;&lt; n];
    int[] tmp = new int[m];
    int sum;
    for (int i = 0; i &lt; m; i++) {
        sum = 0;
        for (char c : seats[i]) {
            sum = sum &lt;&lt; 1;
            if (c == &#39;#&#39;) {
                sum += 1;
            }
        }
        tmp[i] = sum;
    }
    for (int i = m - 1; i &gt;= 0; i--) {
        for (int j = 0; j &lt; 1 &lt;&lt; n; j++) {
            if ((j &amp; (j &lt;&lt; 1)) == 0 &amp;&amp; (j &amp; (j &gt;&gt; 1)) == 0 &amp;&amp; (j &amp; tmp[i]) == 0) {
                for (int k = 0; k &lt; 1 &lt;&lt; n; k++) {
                    if ((k &amp; (j &lt;&lt; 1)) == 0 &amp;&amp; (k &amp; (j &gt;&gt; 1)) == 0) {
                        dp[i][j] = Math.max(dp[i][j], Integer.bitCount(j) + dp[i + 1][k]);
                    }
                }
            }
        }
    }
    int result = 0;
    for (int i = 0; i &lt; 1 &lt;&lt; n; i++) {
        result = Math.max(0, dp[0][i]);
    }
    return result;
}
</code></pre>

<h2 id="toc_4">其他解法</h2>

<p>mark一下，学习:<br/>
<a href="https://leetcode-cn.com/problems/maximum-students-taking-exam/solution/er-fen-tu-zui-da-du-li-ji-by-lightcml/">https://leetcode-cn.com/problems/maximum-students-taking-exam/solution/er-fen-tu-zui-da-du-li-ji-by-lightcml/</a></p>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[169. 求众数]]></title>
    <link href="https://nimitz871016.github.io/15500440104341.html"/>
    <updated>2019-02-13T15:46:50+08:00</updated>
    <id>https://nimitz871016.github.io/15500440104341.html</id>
    <content type="html"><![CDATA[
<p><a href="https://leetcode-cn.com/problems/majority-element/description/">https://leetcode-cn.com/problems/majority-element/description/</a></p>

<pre><code class="language-text">
给定一个大小为 n 的数组，找到其中的众数。众数是指在数组中出现次数大于  ⌊ n/2 ⌋ 的元素。

你可以假设数组是非空的，并且给定的数组总是存在众数。

示例 1:

输入: [3,2,3]
输出: 3
示例 2:

输入: [2,2,1,1,1,2,2]
输出: 2
</code></pre>

<h2 id="toc_0">解题思路：</h2>

<p>&emsp;&emsp;我的思路是，排序，然后逐一比对。直到找到众数为止。但是直觉告诉我应该有更优雅的方式。</p>

<p>&emsp;&emsp;这是一道求众数的问题，有很多种解法，其中我感觉比较好的有两种，一种是用哈希表，这种方法需要O(n)的时间和空间，另一种是用一种叫摩尔投票法 Moore Voting，需要O(n)的时间和O(1)的空间，比前一种方法更好。这种投票法先将第一个数字假设为众数，然后把计数器设为1，比较下一个数和此数是否相等，若相等则计数器加一，反之减一。然后看此时计数器的值，若为零，则将当前值设为候选众数。以此类推直到遍历完整个数组，当前候选众数即为该数组的众数。不仔细弄懂摩尔投票法的精髓的话，过一阵子还是会忘记的，首先要明确的是这个叼炸天的方法是有前提的，就是数组中一定要有众数的存在才能使用，下面我们来看本算法的思路，这是一种先假设候选者，然后再进行验证的算法。我们现将数组中的第一个数假设为众数，然后进行统计其出现的次数，如果遇到同样的数，则计数器自增1，否则计数器自减1，如果计数器减到了0，则更换当前数字为候选者。这是一个很巧妙的设定，也是本算法的精髓所在，为啥遇到不同的要计数器减1呢，为啥减到0了又要更换候选者呢？首先是有那个强大的前提存在，一定会有一个出现超过半数的数字存在，那么如果计数器减到0了话，说明目前不是候选者数字的个数已经跟候选者的出现个数相同了，那么这个候选者已经很weak，不一定能出现超过半数，我们选择更换当前的候选者。那有可能你会有疑问，那万一后面又大量的出现了之前的候选者怎么办，不需要担心，如果之前的候选者在后面大量出现的话，其又会重新变为候选者，直到最终验证成为正确的众数，佩服算法的提出者啊。</p>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[189. 旋转数组]]></title>
    <link href="https://nimitz871016.github.io/15500441189502.html"/>
    <updated>2019-02-13T15:48:38+08:00</updated>
    <id>https://nimitz871016.github.io/15500441189502.html</id>
    <content type="html"><![CDATA[
<pre><code class="language-text">给定一个数组，将数组中的元素向右移动 k 个位置，其中 k 是非负数。

示例 1:

输入: [1,2,3,4,5,6,7] 和 k = 3
输出: [5,6,7,1,2,3,4]
解释:
向右旋转 1 步: [7,1,2,3,4,5,6]
向右旋转 2 步: [6,7,1,2,3,4,5]
向右旋转 3 步: [5,6,7,1,2,3,4]
示例 2:

输入: [-1,-100,3,99] 和 k = 2
输出: [3,99,-1,-100]
解释: 
向右旋转 1 步: [99,-1,-100,3]
向右旋转 2 步: [3,99,-1,-100]
说明:

尽可能想出更多的解决方案，至少有三种不同的方法可以解决这个问题。
要求使用空间复杂度为 O(1) 的原地算法。
</code></pre>

<h2 id="toc_0">解题思路：</h2>

<p>&emsp;&emsp;关键在于O(1)的算法。如果是O(n)的话，方法就太多了。这里就不说了。主要说一下O(1)的做法。</p>

<p>&emsp;&emsp;采用翻转字符的方法，思路是先把前n-k个数字翻转一下，再把后k个数字翻转一下，最后再把整个数组翻转一下：</p>

<p>1 2 3 4 5 6 7 <br/>
4 3 2 1 5 6 7 <br/>
4 3 2 1 7 6 5<br/>
5 6 7 1 2 3 4</p>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[258. 各位相加]]></title>
    <link href="https://nimitz871016.github.io/15500475532566.html"/>
    <updated>2019-02-13T16:45:53+08:00</updated>
    <id>https://nimitz871016.github.io/15500475532566.html</id>
    <content type="html"><![CDATA[
<pre><code class="language-text">
给定一个非负整数  num，反复将各个位上的数字相加，直到结果为一位数。

示例:

输入: 38
输出: 2 
解释: 各位相加的过程为：3 + 8 = 11, 1 + 1 = 2。 由于 2 是一位数，所以返回 2。
进阶:
你可以不使用循环或者递归，且在 O(1) 时间复杂度内解决这个问题吗？
</code></pre>

<h2 id="toc_0">解题思路：</h2>

<p>普通的方法都会解，这里就不重复了。说下O(1)的解法。</p>

<p>这里用到 一个很巧妙的算法。</p>

<p><img src="media/15500475532566/15500475966040.jpg" alt="" style="width:557px;"/></p>

<p>根据以上，可以得出，快速求解，就是将数字不断减去9，直到不能减为止即为正解。</p>

<p>那么不断减去9的过程，可以化为--&gt; mod 9</p>

<p>代码如下：</p>

<pre><code class="language-text">class Solution {
    public int addDigits(int num) {
        if (num == 0){
            return num;
        }
        num %= 9;
        return num == 0 ? 9 : num;
    }
}
</code></pre>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[32. 最长有效括号]]></title>
    <link href="https://nimitz871016.github.io/15480002725541.html"/>
    <updated>2019-01-21T00:04:32+08:00</updated>
    <id>https://nimitz871016.github.io/15480002725541.html</id>
    <content type="html"><![CDATA[
<p><a href="https://leetcode-cn.com/problems/longest-valid-parentheses/description/">https://leetcode-cn.com/problems/longest-valid-parentheses/description/</a></p>

<p>给定一个只包含  &#39;(&#39; 和  &#39;)&#39; 的字符串，找出最长的包含有效括号的子串的长度。</p>

<p>示例 1:</p>

<pre><code class="language-text">输入: &quot;(()&quot;
输出: 2
解释: 最长有效括号子串为 &quot;()&quot;
示例 2:

输入: &quot;)()())&quot;
输出: 4
解释: 最长有效括号子串为 &quot;()()&quot;
</code></pre>

<p>代码：</p>

<pre><code class="language-text">public int longestValidParentheses(String s) {
    int[] dp = new int[s.length()];
    int max = 0;
    for (int i = 1; i &lt; s.length(); i++){
        if (s.charAt(i) == &#39;(&#39;){
            dp[i] = 0;
        }else{
            if (dp[i - 1] != 0) {
                int index = i - dp[i - 1] - 1;
                if (index &gt;= 0 &amp;&amp; s.charAt(index) == &#39;(&#39;) {
                    dp[i] = dp[i - 1] + 2;
                    if (index - 1 &gt;= 0 ){
                        dp[i] += dp[index - 1];
                    }
                }
            }
            if (s.charAt(i - 1) == &#39;(&#39;){
                if (i &gt;= 2) {
                    dp[i] = dp[i] &gt; dp[i - 2] + 2 ? dp[i] : dp[i - 2] + 2;
                }else{
                    dp[i] = 2;
                }
            }
            if (max &lt; dp[i]){
                max = dp[i];
            }
        }
    }
    return max;
}
</code></pre>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[39. 组合总和]]></title>
    <link href="https://nimitz871016.github.io/15480003648444.html"/>
    <updated>2019-01-21T00:06:04+08:00</updated>
    <id>https://nimitz871016.github.io/15480003648444.html</id>
    <content type="html"><![CDATA[
<p><a href="https://leetcode-cn.com/problems/combination-sum/description/">https://leetcode-cn.com/problems/combination-sum/description/</a></p>

<p>给定一个无重复元素的数组  candidates 和一个目标数  target ，找出  candidates 中所有可以使数字和为  target 的组合。</p>

<p>candidates 中的数字可以无限制重复被选取。</p>

<p>说明：</p>

<p>所有数字（包括  target）都是正整数。<br/>
解集不能包含重复的组合。 </p>

<pre><code class="language-text">示例 1:

输入: candidates = [2,3,6,7], target = 7,
所求解集为:
[
  [7],
  [2,2,3]
]
示例 2:

输入: candidates = [2,3,5], target = 8,
所求解集为:
[
  [2,2,2,2],
  [2,3,3],
  [3,5]
]
</code></pre>

<p>题目分析：</p>

<ol>
<li>一看到这道题，就感觉要用递归来做，应该是回溯算法。百度证实无误。</li>
<li>然后自己还没有生写过回溯。想了一会儿，结果不对，和正确解法做了一下比较，修改了一下自己的算法。标答是C++的，在java上需要修改一下，一些特别的地方要处理一下。</li>
</ol>

<p>直接上代码：</p>

<pre><code class="language-text">/**
 * 核心算法
 * @param candidates 数组
 * @param target 当前递归子问题需要计算的target
 * @param start 开始查找的index
 * @param result 当前递归的result数组
 * @param ans 最后的答案
 */
private void findOne(int[] candidates, int target, int start, List&lt;Integer&gt; result, List&lt;List&lt;Integer&gt;&gt; ans){
    if (target == 0){
        List&lt;Integer&gt; list = new ArrayList&lt;&gt;(result); //这里需要新开一个数组，否则会一直复用这个对象，导致结果不对
        ans.add(list);
        return;
    }else if(target &lt; candidates[start]){
        return; //不符合的结果，不处理。
    }else{
        for (int i = start; i &lt; candidates.length; i++){
            result.add(candidates[i]);
            findOne(candidates, target - candidates[i], i, result, ans);
            result.remove(result.size() - 1);
            //这里有点像树的遍历，这里就是要一个节点和不要一个节点的分支。然后可以重复使用元素，所以递归子问题，仍然从i开始。
        }
    }
}

public List&lt;List&lt;Integer&gt;&gt; combinationSum(int[] candidates, int target) {
    List&lt;List&lt;Integer&gt;&gt; ans = new ArrayList&lt;&gt;();
    Arrays.sort(candidates); //题目没有说排好序的数组，所以这里要先拍个序
    findOne(candidates, target, 0, new ArrayList&lt;&gt;(), ans);
    return ans;
}
</code></pre>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[448. 找到所有数组中消失的数字]]></title>
    <link href="https://nimitz871016.github.io/15523931184037.html"/>
    <updated>2019-03-12T20:18:38+08:00</updated>
    <id>https://nimitz871016.github.io/15523931184037.html</id>
    <content type="html"><![CDATA[
<h2 id="toc_0">题目描述</h2>

<p>给定一个范围在  1 ≤ a[i] ≤ n ( n = 数组大小 ) 的 整型数组，数组中的元素一些出现了两次，另一些只出现一次。</p>

<p>找到所有在 [1, n] 范围之间没有出现在数组中的数字。</p>

<p>您能在不使用额外空间且时间复杂度为O(n)的情况下完成这个任务吗? 你可以假定返回的数组不算在额外空间内。</p>

<p>示例:</p>

<p>输入:<br/>
[4,3,2,7,8,2,3,1]</p>

<p>输出:<br/>
[5,6]</p>

<h2 id="toc_1">思路分析</h2>

<p>关键点在于不用额外空间，时间复杂度为O（n）。这两点限制了使用排序算法，或者用额外空间来记录。</p>

<p>那么，不能用额外空间，就只能用原有的数组空间了。这里有一个技巧就是用数组的下标也可以存储信息。因为数据的范围是在n之内，因此数组内出现的数字都是合法的下标。基于这个前提，本题有一个很巧妙的做法。详见代码。</p>

<h2 id="toc_2">代码</h2>

<pre><code class="language-text">public List&lt;Integer&gt; findDisappearedNumbers(int[] nums) {
        List&lt;Integer&gt; result = new ArrayList&lt;&gt;();
        for (int i = 0; i &lt; nums.length; i++){
            //将数组中存放的值作为坐标，进行二次访问。目标是将值设置为相反数（保证是负数）。那么没有变化的位置就是缺失的值
            nums[Math.abs(nums[i])-1] = - Math.abs(nums[Math.abs(nums[i])-1]);
        }
        for (int i = 0; i &lt; nums.length; i++){
            if (nums[i] &gt; 0){
                result.add(i);
            }
        }
        return result;
    }
</code></pre>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[55. 跳跃游戏]]></title>
    <link href="https://nimitz871016.github.io/15486626415496.html"/>
    <updated>2019-01-28T16:04:01+08:00</updated>
    <id>https://nimitz871016.github.io/15486626415496.html</id>
    <content type="html"><![CDATA[
<p>给定一个非负整数数组，你最初位于数组的第一个位置。</p>

<p>数组中的每个元素代表你在该位置可以跳跃的最大长度。</p>

<p>判断你是否能够到达最后一个位置。</p>

<pre><code class="language-text">示例 1:

输入: [2,3,1,1,4]
输出: true
解释: 从位置 0 到 1 跳 1 步, 然后跳 3 步到达最后一个位置。
示例 2:

输入: [3,2,1,0,4]
输出: false
解释: 无论怎样，你总会到达索引为 3 的位置。但该位置的最大跳跃长度是 0 ， 所以你永远不可能到达最后一个位置。
</code></pre>

<p>解题思路：</p>

<p>本道题用贪心。贪心的策略很巧妙。第一次独立没有想出来。<br/>
贪心的策略：</p>

<ol>
<li>每一步都选择最大的可能去接近结果。</li>
<li>那么遍历一遍，如果最大的结果超过了最后一个位置。则说明最后一个位置是可达的，也就是返回true。</li>
</ol>

<p>贪心的巧妙在于，我们不在意到底是如何选择的，只在乎是否可达。</p>

<p>代码：</p>

<pre><code class="language-text"> public boolean canJump(int[] nums) {
    int result = 0;
    if (nums.length == 1){ //注意边界，最后一个台阶的值是没有意义的。因此只有一个台阶的时候，永远是true
        return true;
    }
    for (int i = 0; i &lt; nums.length; i++){
        if (i &gt; result || (nums[i] == 0 &amp;&amp; i == result)){
            break;
        }
        result = Math.max(result, nums[i] + i);
    }
    return result &gt;= nums.length - 1;
}
</code></pre>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[72. 编辑距离]]></title>
    <link href="https://nimitz871016.github.io/15489927924408.html"/>
    <updated>2019-02-01T11:46:32+08:00</updated>
    <id>https://nimitz871016.github.io/15489927924408.html</id>
    <content type="html"><![CDATA[
<h2 id="toc_0">题目</h2>

<p>给定两个单词 word1 和 word2，计算出将 word1 转换成 word2 所使用的最少操作数 。</p>

<p>你可以对一个单词进行如下三种操作：</p>

<p>插入一个字符<br/>
删除一个字符<br/>
替换一个字符<br/>
示例 1:</p>

<p>输入: word1 = &quot;horse&quot;, word2 = &quot;ros&quot;<br/>
输出: 3<br/>
解释: <br/>
horse -&gt; rorse (将 &#39;h&#39; 替换为 &#39;r&#39;)<br/>
rorse -&gt; rose (删除 &#39;r&#39;)<br/>
rose -&gt; ros (删除 &#39;e&#39;)<br/>
示例 2:</p>

<p>输入: word1 = &quot;intention&quot;, word2 = &quot;execution&quot;<br/>
输出: 5<br/>
解释: <br/>
intention -&gt; inention (删除 &#39;t&#39;)<br/>
inention -&gt; enention (将 &#39;i&#39; 替换为 &#39;e&#39;)<br/>
enention -&gt; exention (将 &#39;n&#39; 替换为 &#39;x&#39;)<br/>
exention -&gt; exection (将 &#39;n&#39; 替换为 &#39;c&#39;)<br/>
exection -&gt; execution (插入 &#39;u&#39;)</p>

<h2 id="toc_1">解题思路</h2>

<ol>
<li><p>先作弊了，看到要用dp。思考许久还是没有想法。<br/>
解析一下自己的错误思路。<br/>
看到有三种变换方式，有一点不知道该怎么处理。</p></li>
<li><p>看了网上的解题报告，写下自己理解后的东西。</p>
<p>首先，确认dp的含义<br/>
word1记为s1， word2记为s2<br/>
dp[i][j] = s1 从0...i， s2从0...j 两个字符串的编辑距离。所以dp的大小应该是dp[s1.length + 1][s2.length + 1].</p></li>
</ol>

<ul>
<li><p>转换公式<br/>
计算dp[i][j]有三种变化方式</p>
<ol>
<li>dp[i - 1][j], 由于对比dp[i][j]，s1少了一个，所以要insert一个，编辑距离 + 1</li>
<li>dp[i][j - 1] 由于对比dp[i][j]，s1少了一个，所以要delete一个，编辑距离 + 1</li>
<li>dp[i - 1][j - 1]，对比s1[i - 1] 和 s2[j - 1]的情况，如果相等，编辑距离不变，否则需要+1（替换）</li>
</ol>
<p>从上文可以看出，三种变换方式都有了。</p>
<p>接下来处理边界条件</p>
<p>dp[0][i] = i和dp[i][0] = i，分别代表增加i个，和删除i个。</p></li>
</ul>

<h2 id="toc_2">代码</h2>

<pre><code class="language-text"> public int minDistance(String word1, String word2) {
    int[][] dp = new int[word1.length() + 1][word2.length() + 1];
    for (int i = 0; i &lt;= word1.length(); i++) {
        dp[i][0] = i;
    }
    for (int j = 0; j &lt;= word2.length(); j++) {
        dp[0][j] = j;
    }
    for (int i = 1; i &lt;= word1.length(); i++) {
        for (int j = 1; j &lt;= word2.length(); j++) {
            int a = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1);
            int cost = word1.charAt(i - 1) == word2.charAt(j - 1) ? 0 : 1;
            dp[i][j] = Math.min(a, dp[i - 1][j - 1] + cost);
        }
    }
    return dp[word1.length()][word2.length()];
}
</code></pre>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[JSON反序列化乱序的问题]]></title>
    <link href="https://nimitz871016.github.io/15686164898705.html"/>
    <updated>2019-09-16T14:48:09+08:00</updated>
    <id>https://nimitz871016.github.io/15686164898705.html</id>
    <content type="html"><![CDATA[
<h2 id="toc_0">What</h2>

<p>将Object 转成JSON时，会出现key值的顺序与原顺序不一致的情况（新的顺序按照字母序重新排序）</p>

<h2 id="toc_1">Why</h2>

<p>原因在于HashMap，当用fastjson反序列化时，会使用HashMap。而HashMap底层会根据map中key的hashcode和equals重新排序。</p>

<h2 id="toc_2">How</h2>

<p>使用LinkedHashMap替换HashMap。</p>

<pre><code class="language-text">JSONObject jsonObj = new JSONObject(true);  
Map m= jsonObj.parseObject(json, LinkedHashMap.class);  
</code></pre>

<p>这个方法可以保证第一层的map按照原顺序呗反序列化。想要递归所有层都按照原顺序需要使用</p>

<pre><code class="language-text">HashMap m=JSON.parseObject(json,LinkedHashMap.class,Feature.OrderedField)；
</code></pre>

<h2 id="toc_3">PS</h2>

<p>Q: jackson 会乱序吗？<br/>
A: 不会</p>

<h2 id="toc_4">参考资料</h2>

<p><a href="https://github.com/alibaba/fastjson/issues/954">https://github.com/alibaba/fastjson/issues/954</a><br/>
<a href="https://blog.csdn.net/yj1499945/article/details/78260650">https://blog.csdn.net/yj1499945/article/details/78260650</a></p>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Jackson fasterxml和codehaus的区别]]></title>
    <link href="https://nimitz871016.github.io/15693104259267.html"/>
    <updated>2019-09-24T15:33:45+08:00</updated>
    <id>https://nimitz871016.github.io/15693104259267.html</id>
    <content type="html"><![CDATA[
<p>作为最出名的Json解析库之一，jackson有着两个完全不一样的包名版本。<code>com.fasterxml.jackson</code>&amp;&amp;<code>org.codehaus.jackson</code>。</p>

<p>这两个版本有什么区别呢？</p>

<p>他们是Jackson的两大分支、也是两个版本的不同包名。Jackson从2.0开始改用新的包名fasterxml；1.x版本的包名是codehaus。除了包名不同，他们的Maven artifact id也不同。1.x版本现在只提供bug-fix，而2.x版本还在不断开发和发布中。如果是新项目，建议直接用2x，即fasterxml jackson。</p>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Java中的代理模式]]></title>
    <link href="https://nimitz871016.github.io/15588755745673.html"/>
    <updated>2019-05-26T20:59:34+08:00</updated>
    <id>https://nimitz871016.github.io/15588755745673.html</id>
    <content type="html"><![CDATA[
<h2 id="toc_0">1. 静态代理和动态代理</h2>

<p>&nbsp;&nbsp;&nbsp;&nbsp;本章节参考了<a href="https://www.ibm.com/developerworks/cn/java/j-lo-proxy-pattern/">https://www.ibm.com/developerworks/cn/java/j-lo-proxy-pattern/</a></p>

<h3 id="toc_1">1.1. 定义</h3>

<p>&nbsp;&nbsp;&nbsp;&nbsp;静态代理和动态代理指的是实现代理模式的方式。静态模式意思是所有的代码是静态写好的。而动态代理则相对，部分代码是动态生成的。在动态代理中还分为JDK动态代理和CGLib动态代理。</p>

<h3 id="toc_2">1.2. 关键实现</h3>

<h4 id="toc_3">1.2.1. 静态代理</h4>

<p>静态代理是基于接口实现的，他要求真实类和代理类实现同样的接口。</p>

<pre><code class="language-text">public interface IDBQuery {
    String request();
}
public class DBQuery implements IDBQuery{
    public DBQuery(){
        try{
            Thread.sleep(1000);//假设数据库连接等耗时操作
        }catch(InterruptedException ex){
            ex.printStackTrace();
        }
    }

    @Override
    public String request() {
// TODO Auto-generated method stub
        return &quot;request string&quot;;
    }


}
public class DBQueryProxy implements IDBQuery{
    private DBQuery real = null;

    @Override
    public String request() {
// TODO Auto-generated method stub
//在真正需要的时候才能创建真实对象，创建过程可能很慢
        if(real==null){
            real = new DBQuery();
        }//在多线程环境下，这里返回一个虚假类，类似于 Future 模式
        return real.request();
    }

}
public class Main {
    public static void main(String[] args){
        IDBQuery q = new DBQueryProxy(); //使用代里
        q.request(); //在真正使用时才创建真实对象
    }
}
</code></pre>

<h4 id="toc_4">1.2.2. JDK代理</h4>

<p>当使用JDK代理时，一个最直观的变化就是代理类不需要和真实类实现同一个接口了。取而代之的是代理类实现了InvocationHandler，并Override了invoke方法。在方法里可以统一对实现方法做处理（方法调用前，方法调用后）。</p>

<pre><code class="language-text">import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;


public class DBQueryHandler implements InvocationHandler{
    IDBQuery realQuery = null;//定义主题接口

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
// TODO Auto-generated method stub
        //如果第一次调用，生成真实主题
        if(realQuery == null){
            realQuery = new DBQuery();
        }
        //method.invoke(target, args); 执行调用的方法。
        //返回真实主题完成实际的操作
        return realQuery.request();
    }
    public static IDBQuery createProxy(){
        IDBQuery proxy = (IDBQuery)Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{IDBQuery.class}, new DBQueryHandler()); // 注意，生成的代理类实例被强转为IDBQuery
        return proxy;
    }
}
</code></pre>

<h4 id="toc_5">1.2.3. CGLib代理</h4>

<p>CGLib一个直观的最大的特点就是真实类无需实现接口（当然实现了也没关系）。</p>

<p>接口类</p>

<pre><code class="language-text">public interface BookProxy {
    public void addBook();
}
</code></pre>

<p>实现类</p>

<pre><code class="language-text">//该类并没有申明 BookProxy 接口
public class BookProxyImpl {
    public void addBook() { 
        System.out.println(&quot;增加图书的普通方法...&quot;); 
    } 
}
</code></pre>

<p>代理类</p>

<pre><code class="language-text">import java.lang.reflect.Method; 
import net.sf.cglib.proxy.Enhancer; 
import net.sf.cglib.proxy.MethodInterceptor; 
import net.sf.cglib.proxy.MethodProxy;

public class BookProxyLib implements MethodInterceptor {
    private Object target;
    /**
     * 创建代理对象 
     *
     * @param target
     * @return
     */
    public Object getInstance(Object target) {
        this.target = target;
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(this.target.getClass());
        // 回调方法 
        enhancer.setCallback(this);
        // 创建代理对象 
        return enhancer.create();
    }

    @Override
// 回调方法 
    public Object intercept(Object obj, Method method, Object[] args,
                            MethodProxy proxy) throws Throwable {
        System.out.println(&quot;事物开始&quot;);
        proxy.invokeSuper(obj, args);
        System.out.println(&quot;事物结束&quot;);
        return null;
    }
}
</code></pre>

<p>调用方法</p>

<pre><code class="language-text">public class TestCglib { 
    public static void main(String[] args) { 
        BookProxyLib cglib=new BookProxyLib(); 
        BookProxyImpl bookCglib=(BookProxyImpl)cglib.getInstance(new BookProxyImpl()); 
        bookCglib.addBook();  //可以看到BookProxyLib并没有声明BookProxy接口，但是仍然可以调用addBook方法
    } 
}
</code></pre>

<h3 id="toc_6">1.3. 区别和共同点</h3>

<p>静态代理是通过在代码中显式定义一个业务实现类一个代理，在代理类中对同名的业务方法进行包装，用户通过代理类调用被包装过的业务方法；</p>

<p>JDK动态代理是通过接口中的方法名，在动态生成的代理类中调用业务实现类的同名方法；</p>

<p>CGlib动态代理是通过继承业务类，生成的动态代理类是业务类的子类，通过重写业务方法进行代理；</p>

<p><a href="https://blog.csdn.net/neosmith/article/details/51072840">https://blog.csdn.net/neosmith/article/details/51072840</a></p>

<h2 id="toc_7">2. 实际应用场景举例</h2>

<p>todo:没有理解cglib不用真实类实现接口的意义。因为真实类没有实现接口，但是暴露了public的方法。这和直接调用有啥区别？</p>

<p>另外cglib的试用场景，真实类没有实现的接口意义何在？如果没有接口来规范统一的调用逻辑，例如一堆的实现类实现了A接口，因此必须实现A接口中定义的B方法。这样才有意义吧？</p>

<p>查看spring源码，了解spring中，cglib的使用方法，来解答上述疑问。</p>

<p>代理模式的意义</p>

<ol>
<li>有代理，便于解耦</li>
<li>静态代理太麻烦，每个都要</li>
<li>JDK代理受限于要实现接口</li>
<li>CGLib不需要实现接口，看上去无法统一接口的方法，但是可能是用在一些common的方法，例如Object的方法。用在类创建时刻。</li>
<li>另外让方法运行只是最基本的，代理模式的最大用途是管理原方法的运行前，后，时（切面，AOP）。</li>
</ol>

<h2 id="toc_8">3. Spring AOP和动态代理</h2>

<h2 id="toc_9">4. OC中的动态代理模式浅谈</h2>

<h2 id="toc_10">5. CGLib和JDK代理的性能对比</h2>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Java并发编程]]></title>
    <link href="https://nimitz871016.github.io/15479962056718.html"/>
    <updated>2019-01-20T22:56:45+08:00</updated>
    <id>https://nimitz871016.github.io/15479962056718.html</id>
    <content type="html"><![CDATA[
<p>&emsp;&emsp;本文对Java中的并发变成进行了简单的描述。是本人阅读《Java并发编程的艺术》一书的读书笔记。本文对重要的概念进行了记录。</p>

<p>&emsp;&emsp;本文首先介绍了各种各样的和锁相关的概念。然后介绍了Java多线程的技术要点，最后介绍了一些经典使用案例。</p>

<span id="more"></span><!-- more -->

<h2 id="toc_0">0. 重要技术要点目录</h2>

<h3 id="toc_1">0.1 和锁有关的概念</h3>

<p>&emsp;&emsp;关于锁的概念有很多，这里一一列举。</p>

<blockquote>
<p>按照锁的的状态分</p>
</blockquote>

<p>&emsp;&emsp;在Java中，按照锁的状态分，有四种状态。分别是：</p>

<ol>
<li>无锁</li>
<li>偏向锁</li>
<li>轻量级锁</li>
<li>重量级锁</li>
</ol>

<p>偏向锁是可以取消的，根据系统的实际使用情况。因为可能有的场景，系统中资源冲突严重，那么偏向锁就没有什么必要存在了，反而增加处理逻辑，更慢。</p>

<blockquote>
<p>按照锁的实现方式分</p>
</blockquote>

<ol>
<li>乐观锁</li>
<li>悲观锁</li>
</ol>

<p>&emsp;&emsp;在Java中没有特地讲乐观锁，悲观锁。但实际上这两种锁是可以和Java中提到的概念一一对应。</p>

<blockquote>
<p>按照试用场景分</p>
</blockquote>

<ol>
<li>可重入锁--ReentranLock</li>
<li>读写锁--ReentranReadWriteLock</li>
</ol>

<p>&emsp;&emsp;这两种锁是可以直接和Java中的实现对应的。</p>

<blockquote>
<p>其他</p>
</blockquote>

<ul>
<li>volatile和synchronized</li>
</ul>

<p>&emsp;&emsp;这两个关键字是Java锁的基础（很多其他语言也有相同的使用）。例如可重入锁和读写锁其实都是对这两个关键词的应用，并不是另起炉灶。</p>

<ul>
<li>CAS操作</li>
</ul>

<p>&emsp;&emsp;CAS（Compare and swap）是一种乐观锁的操作方式。CAS操作保证了线程安全，同时没有对线程加锁。是一种常见的优化锁使用的选择。</p>

<ul>
<li>happens-before原则</li>
</ul>

<p>指令重排的相关概念。指的是按照编码逻辑的指令的执行顺序。A指令happens-beforeB指令。那么一个重要的原则是，判断是否可以重排的依据，并不是要求指令一定要按照物理顺序。只要重排后的指令执行结果和重排前一致就可以。</p>

<h2 id="toc_2">1. Java中的几种锁</h2>

<p>读写锁<br/>
重入锁</p>

<h2 id="toc_3">2. Java中的锁的状态</h2>

<p><img src="media/15474517017096/15474518978931.jpg" alt="" style="width:608px;"/></p>

<p><img src="media/15474517017096/15476277957012.jpg" alt="" style="width:595px;"/></p>

<p>锁会升级不会降级。越“轻”的锁，效率越高。但是越不好解决同步的问题（不能解决的时候就要升级）。<br/>
偏向锁就是偏向于拿锁的线程通常是同一个，这样就可以提高效率。</p>

<p><img src="media/15474517017096/15476312618712.jpg" alt="" style="width:595px;"/></p>

<p>Q:红框处为什么要检查线程是否活着？<br/>
A:线程1执行完毕后,不会主动去释放偏向锁。</p>

<p>Q:偏向锁是如何升级成轻量级锁的？<br/>
A:当另一个线程获取偏向锁失败，就升级成轻量级锁。轻量级锁的特点是自旋。</p>

<p>Q:轻量级锁是如何升级成重量级锁的？<br/>
A:轻量级锁自旋到达一定条件就会升级成重量级锁。但是具体条件是啥，没有找到相关资料。</p>

<p>总结，获取不到锁就会往上膨胀。直到重量级锁。</p>

<p>通俗来讲就是：</p>

<p>偏向锁：仅有一个线程进入临界区<br/>
轻量级锁：多个线程交替进入临界区<br/>
重量级锁：多个线程同时进入临界区</p>

<p><strong>问题</strong><br/>
轻量级锁是自旋的，那么根据什么条件判断会膨胀成重量级锁</p>

<h2 id="toc_4">3. volatile</h2>

<p>使用volatile修饰变量，可以让这个变量在各个线程之间永远显示的是最新的值。为什么会有这么一个说法？每一个线程有自己的独立缓存。对于一个共享变量，在多线程并发的情况下，可能导致自己的缓存和实际的值不等。而volaile的出现，使得线程自己的缓存无效，每一次都需要去内存中读取最新的值。<br/>
volatile可以看做轻量的synchronized。可以减少锁的程度。提高性能。</p>

<h3 id="toc_5">3. volatile实现原理的相关知识</h3>

<ol>
<li>happens-before原则，涉及到volatile的底层实现原理。</li>
</ol>

<h3 id="toc_6">4. happens-before原则</h3>

<p><img src="media/15474517017096/15476423173260.jpg" alt="" style="width:598px;"/></p>

<p>所以本质的意思就是指令重排不能影响计算结果。只要计算结果一样，虽然有happens-before关系，也不要求一定要按照happens-before的顺序执行<br/>
<img src="media/15474517017096/15476476024054.jpg" alt="" style="width:592px;"/></p>

<p><img src="media/15474517017096/15476561471183.jpg" alt="" style="width:593px;"/></p>

<h3 id="toc_7">volatile的经典应用-单例模式</h3>

<p><img src="media/15474517017096/15476573891697.jpg" alt="" style="width:601px;"/></p>

<h3 id="toc_8">wait()和notify()</h3>

<ol>
<li><p>作用</p></li>
<li><p>好处。<br/>
wait() 等待。相比一个死循环，定期休眠来判断是否结束要好的多，因为定期休眠可能会导致响应不及时。</p></li>
<li><p>notify() 之后，并不是对应的wait会立刻结束</p></li>
</ol>

<p><img src="media/15474517017096/15476614597968.jpg" alt="" style="width:597px;"/></p>

<h3 id="toc_9">ThreadLocal</h3>

<h3 id="toc_10">线程池技术</h3>

<p>几种线程池的创建方式，以及适应的场景。</p>

<h3 id="toc_11">锁和synchronized的关系</h3>

<p><img src="media/15474517017096/15476947781313.jpg" alt="" style="width:599px;"/></p>

<p><img src="media/15474517017096/15476953591669.jpg" alt="" style="width:604px;"/></p>

<h3 id="toc_12">ReentrantLock 和 ReetranReadWriteLock 可重入锁和可重入读写锁</h3>

<p>可重入的意思是可以被同一个线程重复加锁。<br/>
读写锁适用于生产者消费者场景。分为读锁和写锁。</p>

<h3 id="toc_13">Object的wait，notify和condition的await，signal</h3>

<p><img src="media/15474517017096/15477091389472.jpg" alt="" style="width:958px;"/></p>

<h3 id="toc_14">ConcurrentHashMap分析</h3>

<p>使用了锁来保证线程安全。使用了锁分段技术。减少了锁的冲突，从而提高了效率。</p>

<h3 id="toc_15">ConcurrentLinkedQueue分析</h3>

<p><del>学习其特别的尾节点定义方式。可以很大程度减少冲突。其使用CAS操作来避免冲突。但是其特别的尾节点定义方式，使得CAS操作不那么容易失败。</del></p>

<p><img src="media/15474517017096/15477115519893.jpg" alt="" style="width:603px;"/></p>

<p>出队也有相似的逻辑</p>

<h3 id="toc_16">Java中的阻塞队列</h3>

<p><img src="media/15474517017096/15477148083195.jpg" alt="" style="width:418px;"/></p>

<h4 id="toc_17">DelayQueue</h4>

<p>可以支持延时获取元素的队列，要求队列中的元素必须实现Delayed接口</p>

<ul>
<li>经典场景</li>
</ul>

<p>缓存系统</p>

<p>定时任务调度</p>

<h4 id="toc_18">SynchronousQueue</h4>

<p><img src="media/15474517017096/15477162270641.jpg" alt="" style="width:600px;"/></p>

<h4 id="toc_19">LinkedBlockingQueue</h4>

<p>用在工作窃取模式</p>

<h4 id="toc_20">CountDownLatch join的升级版</h4>

<h3 id="toc_21">Semaphore</h3>

<p><img src="media/15474517017096/15477221892006.jpg" alt="" style="width:601px;"/></p>

<h3 id="toc_22">线程池</h3>

<ol>
<li>队列模型的选取
<ol>
<li>建议使用有界队列</li>
<li>建议和系统的核心数挂钩？？</li>
</ol></li>
<li>饱和策略的选取和试用场景</li>
</ol>

<h3 id="toc_23">乐观锁，悲观锁</h3>

<p>乐观锁就是CAS操作</p>

<p>悲观锁就是synchronized</p>

<p>乐观锁认为读大于写，不用一下子就把线程锁了。而是尝试一下。</p>

<p>悲观锁认为系统冲突严重，必须锁。</p>

<p>CAS竞争锁<br/>
CAS操作<br/>
偏向锁<br/>
轻量级锁<br/>
乐观锁<br/>
悲观锁<br/>
volatile 和 ++<br/>
threadLocal<br/>
threadpool<br/>
condition 是和lock相配合的<br/>
相比wait，notify是和synchronized配合的</p>

<p>锁分段技术，代表concurrentHashMap</p>

<p>各种queue的特点，区别，和使用场景。最好有范例。各种queue的试用场景</p>

<p>线程池</p>

<p>结合阿里的java开发手册中关于线程池的部分结合理解</p>

<p>工作线程worker</p>

<p>threadLocal 和 volatile变量</p>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Java序列化的那些事]]></title>
    <link href="https://nimitz871016.github.io/15842883217984.html"/>
    <updated>2020-03-16T00:05:21+08:00</updated>
    <id>https://nimitz871016.github.io/15842883217984.html</id>
    <content type="html"><![CDATA[
<h2 id="toc_0">1. 什么是序列化和反序列化</h2>

<p>（1）Java序列化是指把Java对象转换为字节序列的过程，而Java反序列化是指把字节序列恢复为Java对象的过程；</p>

<p>（2）<strong>序列化：</strong>对象序列化的最主要的用处就是在传递和保存对象的时候，保证对象的完整性和可传递性。序列化是把对象转换成有序字节流，以便在网络上传输或者保存在本地文件中。序列化后的字节流保存了Java对象的状态以及相关的描述信息。序列化机制的核心作用就是对象状态的保存与重建。</p>

<p>（3）<strong>反序列化：</strong>客户端从文件中或网络上获得序列化后的对象字节流后，根据字节流中所保存的对象状态及描述信息，通过反序列化重建对象。</p>

<p>（4）本质上讲，序列化就是把实体对象状态按照一定的格式写入到有序字节流，反序列化就是从有序字节流重建对象，恢复对象状态。</p>

<h2 id="toc_1">2、为什么需要序列化与反序列化</h2>

<p>我们知道，当两个进程进行远程通信时，可以相互发送各种类型的数据，包括文本、图片、音频、视频等， 而这些数据都会以二进制序列的形式在网络上传送。</p>

<p>那么当两个Java进程进行通信时，能否实现进程间的对象传送呢？答案是可以的！如何做到呢？这就需要Java序列化与反序列化了！</p>

<p>换句话说，一方面，发送方需要把这个Java对象转换为字节序列，然后在网络上传送；另一方面，接收方需要从字节序列中恢复出Java对象。</p>

<p>当我们明晰了为什么需要Java序列化和反序列化后，我们很自然地会想Java序列化的好处。其好处一是实现了数据的持久化，通过序列化可以把数据永久地保存到硬盘上（通常存放在文件里），二是，利用序列化实现远程通信，即在网络上传送对象的字节序列。</p>

<p>总的来说可以归结为以下几点：</p>

<p>（1）永久性保存对象，保存对象的字节序列到本地文件或者数据库中；<br/>
（2）通过序列化以字节流的形式使对象在网络中进行传递和接收；<br/>
（3）通过序列化在进程间传递对象；</p>

<h2 id="toc_2">3、实现Java对象序列化与反序列化的方法</h2>

<p>假定一个User类，它的对象需要序列化，可以有如下三种方法：</p>

<p>（1）若User类仅仅实现了Serializable接口，则可以按照以下方式进行序列化和反序列化</p>

<p>ObjectOutputStream采用默认的序列化方式，对User对象的非transient的实例变量进行序列化。<br/>
ObjcetInputStream采用默认的反序列化方式，对对User对象的非transient的实例变量进行反序列化。</p>

<p>（2）若User类仅仅实现了Serializable接口，并且还定义了readObject(ObjectInputStream in)和writeObject(ObjectOutputSteam out)，则采用以下方式进行序列化与反序列化。</p>

<p>ObjectOutputStream调用User对象的writeObject(ObjectOutputStream out)的方法进行序列化。<br/>
ObjectInputStream会调用User对象的readObject(ObjectInputStream in)的方法进行反序列化。</p>

<p>（3）若User类实现了Externalnalizable接口，且User类必须实现readExternal(ObjectInput in)和writeExternal(ObjectOutput out)方法，则按照以下方式进行序列化与反序列化。</p>

<p>ObjectOutputStream调用User对象的writeExternal(ObjectOutput out))的方法进行序列化。<br/>
ObjectInputStream会调用User对象的readExternal(ObjectInput in)的方法进行反序列化。</p>

<h2 id="toc_3">4. serialVersionUID</h2>

<p>序列化运行时使用一个称为 serialVersionUID 的版本号与每个可序列化类相关联，该序列号在反序列化过程中用于验证序列化对象的发送者和接收者是否为该对象加载了与序列化兼容的类。为它赋予明确的值。显式地定义serialVersionUID有两种用途：</p>

<p>在某些场合，希望类的不同版本对序列化兼容，因此需要确保类的不同版本具有相同的serialVersionUID；</p>

<p>在某些场合，不希望类的不同版本对序列化兼容，因此需要确保类的不同版本具有不同的serialVersionUID。</p>

<h2 id="toc_4">参考资料</h2>

<p>原文链接：<a href="https://blog.csdn.net/xlgen157387/article/details/79840134">https://blog.csdn.net/xlgen157387/article/details/79840134</a><br/>
<a href="https://blog.csdn.net/u014750606/article/details/80040130">https://blog.csdn.net/u014750606/article/details/80040130</a></p>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[MongoDB的启动和停止]]></title>
    <link href="https://nimitz871016.github.io/15479997745467.html"/>
    <updated>2019-01-20T23:56:14+08:00</updated>
    <id>https://nimitz871016.github.io/15479997745467.html</id>
    <content type="html"><![CDATA[
<h2 id="toc_0">1. 启动</h2>

<ol>
<li>用命令行，加各种的特殊参数</li>
<li><p>用命令行，但是所有的参数都放在一个配置文件中。如</p>
<pre><code class="language-text">mongod -f /etc/mongod.conf
</code></pre></li>
<li><p>用传统的service mongod start</p></li>
</ol>

<h3 id="toc_1">1.1. service mongod start</h3>

<ul>
<li>该命令本身没有任何问题。只需要注意配置启动的时候执行的语句就可以达到同样的效果。在实际使用的过程中，并不会频繁的变更需求和配置，因此用service来启动足以。</li>
<li><p>另外如果用命令行启动，还存在权限问题。<br/>
用mongod启动了以后，权限全部变为root。这时候再用service mongod start来启动就会有各种各样的权限问题。</p></li>
<li><p>service start启动的脚本</p>
<pre><code class="language-text">/usr/lib/systemd/system/mongod.service
</code></pre></li>
</ul>

<pre><code class="language-text">[Unit]
Description=MongoDB Database Server
After=network.target
Documentation=https://docs.mongodb.org/manual

[Service]
User=mongod
Group=mongod
Environment=&quot;OPTIONS=-f /etc/mongod.conf&quot;
EnvironmentFile=-/etc/sysconfig/mongod
ExecStart=/usr/bin/mongod $OPTIONS
ExecStartPre=/usr/bin/mkdir -p /var/run/mongodb
ExecStartPre=/usr/bin/chown mongod:mongod /var/run/mongodb
ExecStartPre=/usr/bin/chmod 0755 /var/run/mongodb
PermissionsStartOnly=true
PIDFile=/var/run/mongodb/mongod.pid
Type=forking
# file size
LimitFSIZE=infinity
# cpu time
LimitCPU=infinity
# virtual memory size
LimitAS=infinity
# open files
LimitNOFILE=64000
# processes/threads
LimitNPROC=64000
# locked memory
LimitMEMLOCK=infinity
# total threads (user+kernel)
TasksMax=infinity
TasksAccounting=false
# Recommended limits for for mongod as specified in
# http://docs.mongodb.org/manual/reference/ulimit/#recommended-settings

[Install]
WantedBy=multi-user.target
</code></pre>

<h2 id="toc_2">2. 停止</h2>

<p>强制关闭MongoDB（不建议使用）；<br/>
service mongod stop<br/>
或者，从MongoDB的admin中关闭（推荐用这种方法）：</p>

<pre><code class="language-text">&gt;use admin
switched to db admin
&gt;db.shutdownServer()
server should be down...
</code></pre>

<p>或者</p>

<pre><code class="language-text">mongod --shutdown
</code></pre>

<p>使用shutdownServer关闭MongoDB，如有MongoDB主从服务器，则在服务关闭前同步主从服务器；强制关闭则不会；</p>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[RAC中的宏定义魔法]]></title>
    <link href="https://nimitz871016.github.io/15480007861723.html"/>
    <updated>2019-01-21T00:13:06+08:00</updated>
    <id>https://nimitz871016.github.io/15480007861723.html</id>
    <content type="html"><![CDATA[
<p><a href="https://onevcat.com/2014/01/black-magic-in-macro/">https://onevcat.com/2014/01/black-magic-in-macro/</a></p>

<h2 id="toc_0">1. #的作用</h2>

<h2 id="toc_1">2. ##的作用</h2>

<h2 id="toc_2">3. <strong>VA_ARGS</strong>的作用</h2>

<h2 id="toc_3">4. RAC()</h2>

<h3 id="toc_4">4.1 函数功能</h3>

<p>RAC(),有两种调用方式，一个是两个参数，一个是三个参数的。函数的第一个参数代表着要绑定的对象，第二个参数要绑定的对象的属性，第三个参数代表当追踪的结果为nil时，应该赋予的值。</p>

<p>这里就有一个很有趣的问题，ReactiveCocoa是如何做到用一个函数宏，自动识别参数个数，并调用正确的函数的呢？</p>

<span id="more"></span><!-- more -->

<h3 id="toc_5">4.2 实现解析</h3>

<p>我们通过一个具体的例子来解析RAC()的宏定义魔法。我们在程序中如此调用：<br/>
RAC(self.startButton, enabled) = RACObserve(self.viewModel, canStartTimer);</p>

<p>我们来看下，RAC中的宏定义魔法，干了些什么。RAC(TARGET,...)第一步被解析为:</p>

<pre><code class="language-text">RAC(TARGET,...) metamacro_if_eq(1, metamacro_argcount(__VA_ARGS__)) \
        (RAC_(TARGET, __VA_ARGS__, nil)) \
        (RAC_(TARGET, __VA_ARGS__))
</code></pre>

<p>其中,TARGET为<code>self.startButton</code>,那么<code>metamacro_argcount(__VA_ARGS__)</code> 从函数的名字，我们知道其作用是获取参数的个数。我们来看下具体是怎样获得参数的个数的。</p>

<p>本例中为</p>

<pre><code class="language-text">metamacro_argcount(enabled)
</code></pre>

<p>定义为</p>

<pre><code class="language-text">#define metamacro_argcount(...) \
        metamacro_at(20, __VA_ARGS__, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)
</code></pre>

<p>本例中为</p>

<pre><code class="language-text">metamacro_at(20,enabled,20,19,18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)
</code></pre>

<p>其中</p>

<pre><code class="language-text">metamacro_at(20, __VA_ARGS__, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)
</code></pre>

<p>的定义为</p>

<pre><code class="language-text">#define metamacro_at(N, ...) \
        metamacro_concat(metamacro_at, N)(__VA_ARGS__)
</code></pre>

<p>其中</p>

<pre><code class="language-text">metamacro_concat(metamacro_at, N)(__VA_ARGS__)
</code></pre>

<p>其中</p>

<pre><code class="language-text">metamacro_concat
</code></pre>

<p>的定义为</p>

<pre><code class="language-text">#define metamacro_concat(A, B) \
        metamacro_concat_(A, B)
--&gt;
#define metamacro_concat_(A, B) A ## B
</code></pre>

<p>因此，逆推回去</p>

<pre><code class="language-text">metamacro_concat(metamacro_at, N)(__VA_ARGS__)
--&gt;
metamacro_atN(__VA_ARGS)
--&gt;
metamacro_at20(__VA_ARGS)
</code></pre>

<p>本例中为 </p>

<pre><code class="language-text">metamacro_at20(enabled，20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)
拼接处的函数metamacro_at20
</code></pre>

<p>，其定义为</p>

<pre><code class="language-text">#define metamacro_at20(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, ...) metamacro_head(__VA_ARGS__)
</code></pre>

<p>本例中为</p>

<pre><code class="language-text">metamacro_head(1)
</code></pre>

<p>其中</p>

<pre><code class="language-text">metamacro_head(__VA_ARGS__)
</code></pre>

<p>其定义为</p>

<pre><code class="language-text">metamacro_head_(__VA_ARGS__, 0)
</code></pre>

<p>封装了一层，真实为</p>

<pre><code class="language-text">#define metamacro_head_(FIRST, ...) FIRST
</code></pre>

<p>本例中为 <code>1</code></p>

<p>也就是<code>metamacro_argcount(enabled)</code> 的结果为 1。</p>

<p>回到RAC的第一层定义，现在的解析式为</p>

<pre><code class="language-text">metamacro_if_eq(1, 1) \

        (RAC_(TARGET, __VA_ARGS__, nil)) \

        (RAC_(TARGET, __VA_ARGS__))
</code></pre>

<p>下面分析<code>metamacro_if_eq</code>,其定义为</p>

<pre><code class="language-text">#define metamacro_if_eq(A, B) \
        metamacro_concat(metamacro_if_eq, A)(B)
</code></pre>

<p>其中A写死为1，B本例中计算结果为1，本例中变为</p>

<pre><code class="language-text">metamacro_if_eq(1,1) (RAC_(TARGET, __VA_ARGS__, nil)) (RAC_(TARGET, __VA_ARGS__))
</code></pre>

<p>拼接结果为:</p>

<pre><code class="language-text">metamacro_if_eq1(B)
</code></pre>

<p>本例中为:</p>

<pre><code class="language-text">metamacro_if_eq1(1)
</code></pre>

<p>其定义为:</p>

<pre><code class="language-text">#define metamacro_if_eq1(VALUE) metamacro_if_eq0(metamacro_dec(VALUE))
</code></pre>

<p>其中</p>

<pre><code class="language-text">#define metamacro_dec(VAL) \
        metamacro_at(VAL, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19)
</code></pre>

<p>本例中为：</p>

<pre><code class="language-text">metamacro_at(1,-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19)
</code></pre>

<p>拼接后为（metamacro_at的作用上面已经讲过了）</p>

<pre><code class="language-text">metamacro_at1(-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19)
</code></pre>

<p>其定义为</p>

<pre><code class="language-text">#define metamacro_at1(_0, ...) metamacro_head(__VA_ARGS__)

--&gt; metamacro_head(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19)

--&gt; metamacro_head_(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 0)

--&gt; 0

--&gt; 合并

metamacro_if_eq0（0）
</code></pre>

<p>其中</p>

<pre><code class="language-text">#define metamacro_if_eq0(VALUE) \

    metamacro_concat(metamacro_if_eq0_, VALUE)
</code></pre>

<p>本例中为<code>metamacro_if_eq0_0</code>。<br/>
回到原式，与后面的部分拼接结果为</p>

<pre><code class="language-text">#define RAC(TARGET, ...) \
    metamacro_if_eq(1, metamacro_argcount(__VA_ARGS__)) \
        (RAC_(TARGET, __VA_ARGS__, nil)) \
        (RAC_(TARGET, __VA_ARGS__))
        
--&gt; metamacro_if_eq0_0 (RAC_(TARGET, __VA_ARGS__, nil)) (RAC_(TARGET, __VA_ARGS__))
</code></pre>

<p><code>metamacro_if_eq0_0</code>定义为</p>

<pre><code class="language-text">#define metamacro_if_eq0_0(...) __VA_ARGS__ metamacro_consume_
</code></pre>

<p>在本例中化为</p>

<pre><code class="language-text">(RAC_(TARGET, __VA_ARGS__, nil)) metamacro_consume_ (RAC_(TARGET, __VA_ARGS__))
--&gt; (RAC_(TARGET, __VA_ARGS__, nil))
--&gt; (RAC_(self.startButton, enabled, nil))
</code></pre>

<p>ps:</p>

<p>我们再看下，如果参数不为1的情况（这里的1的计数排除了第一项，在本例中排除的是<code>self.startButton</code>）。</p>

<p>如果是不止两个参数的情况那么,设参数个数为x，x&lt;&gt;1,则原式化为</p>

<pre><code class="language-text">metamacro_if_eq(1,x) (RAC_(TARGET, __VA_ARGS__, nil)) (RAC_(TARGET, __VA_ARGS__))

--&gt; metamacro_if_eq1(x)

--&gt; metamacro_at(x,-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19)

</code></pre>

<p>这里假设x为2(注意这里x只要不是取1，宏定义展开都是一样的,而在RAC()这个宏，x只可能取1或2)，则</p>

<pre><code class="language-text">--&gt; metamacro_at2(-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19)

--&gt; metamacro_head(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19)

--&gt; 3

--&gt; metamacro_if_eq0_3(RAC_(TARGET, __VA_ARGS__, nil)) (RAC_(self.startButton, enabled, nil))
</code></pre>

<p>由 <code>metamacro_if_eq0_3()</code>的定义，前面一项被吃掉了，所以原式只剩了后一项</p>

<pre><code class="language-text">--&gt; (RAC_(self.startButton, enabled, nil))
</code></pre>

<p>至此，由RAC(...)至RAC_(...)的解析完毕！这个宏的作用就是根据参数的个数选择调用函数式子的不同，如果只有1个参数的，就调用<code>(RAC_(self.startButton, enabled, nil))</code>，如果有多个参数的就调用<code>(RAC_(TARGET, __VA_ARGS__))</code>。</p>

<h2 id="toc_6">5. RACObserve()</h2>

<h3 id="toc_7">5.1. 函数的功能</h3>

<p>让我们再看下4.2中提到的例子</p>

<pre><code class="language-text">RAC(self.startButton, enabled) = RACObserve(self.viewModel, canStartTimer);
</code></pre>

<p>等式的左边的意思是我关注的是<code>self.startButton</code>的enabled属性，我需要将其与<code>self.viewModel</code>的canStartTimer属性绑定起来。那么右边实际上是个KVO。一旦<code>self.viewModel的canStartTimer</code>属性发生变化，<code>self.startButton</code>的enabled相应的发生变化。</p>

<h3 id="toc_8">5.2 函数解析</h3>

<p>让我们看下ReactiveCocoa是怎么做到的。<br/>
我们的例子是<code>RACObserve(self.viewModel, canStartTimer)</code>;<br/>
RACObserve()的定义是</p>

<pre><code class="language-text">#define RACObserve(TARGET, KEYPATH) \
    [(id)(TARGET) rac_valuesForKeyPath:@keypath(TARGET, KEYPATH) observer:self]
</code></pre>

<p>因此，在本例中解析为</p>

<pre><code class="language-text">[(id)(self.viewModel) rac_valuesForKeyPath:@keypath(self.viewModel, canStartTimer) observer:self]
</code></pre>

<p>OK，原来是<code>self.viewModel</code>发送<code>rac_valuesForKeyPath: observer:</code>消息。等等,@keypath是什么鬼？别急，@keypath定义如下：</p>

<pre><code class="language-text">#define keypath(...) \
    metamacro_if_eq(1, metamacro_argcount(__VA_ARGS__))(keypath1(__VA_ARGS__))(keypath2(__VA_ARGS__))
</code></pre>

<p>哈哈，原来是我们之前解析过的宏，那么很明显了，如果keypath中的参数个数等于1，那么调用keypath1,否则调用keypath2。在本例中，参数是2。因此应该调用keypath2。</p>

<pre><code class="language-text">#define keypath2(OBJ, PATH) \
    (((void)(NO &amp;&amp; ((void)OBJ.PATH, NO)), # PATH))
</code></pre>

<p>带入例子，化为</p>

<pre><code class="language-text">(((void)(NO &amp;&amp; ((void)self.viewModel.canStartTimer, NO)), # canStartTimer))
</code></pre>

<p>处理一下#操作符，化为</p>

<pre><code class="language-text">(((void)(NO &amp;&amp; ((void)self.viewModel.canStartTimer, NO)), &quot;canStartTimer&quot;))
</code></pre>

<p>TODO: @keyPath宏未能解析其生效的原理。根据其注解，这个宏的作用是验证参数的合法性，最后返回的结果是一个NSString，即keypath<br/>
因此，RACObserve()的作用就是发送<code>rac_valuesForKeyPath: observer:</code>消息。其中根据传入的keypath参数个数，做了一些处理。<br/>
接下来，我们分析一下<code>rac_valuesForKeyPath: observer:</code>的作用</p>

<p>其函数声明为：</p>

<pre><code class="language-text">- (RACSignal *)rac_valuesForKeyPath:(NSString *)keyPath observer:(NSObject *)observer;
定义为
- (RACSignal *)rac_valuesForKeyPath:(NSString *)keyPath observer:(NSObject *)observer {
    return [[[self rac_valuesAndChangesForKeyPath:keyPath options:NSKeyValueObservingOptionInitial observer:observer] reduceEach:^(id value, NSDictionary *change) {
        return value;
    }] setNameWithFormat:@&quot;RACObserve(%@, %@)&quot;, self.rac_description, keyPath];
}
</code></pre>

<h2 id="toc_9">6.@strongify, @ weakify &amp; @unsafeify</h2>

<p>@strongify的使用，必须在@weakify使用之后，是成对的操作<br/>
6.1 @weakify</p>

<p>weakify 定义如下</p>

<pre><code class="language-text">#define weakify(...) \
    autoreleasepool {} \
    metamacro_foreach_cxt(rac_weakify_,, __weak, __VA_ARGS__)
</code></pre>

<p>我们的例子是</p>

<pre><code class="language-text">@weakify(self)
--&gt; autoreleasepool {} \
    metamacro_foreach_cxt(rac_weakify_,, __weak, self)
</code></pre>

<p>注意这里有两个非法的地方，</p>

<ol>
<li>rac_weakify_没有参数，这本来是非法的，但是此处编译器还不会检查，因此其真正的展开在之后，我们先列出其定义。</li>
<li>metamacro_foreach_cxt(rac_weakify_,, __weak, self)中有两个连续逗号，第二个参数失踪了。这是放空的意思。注意，放空是可以的，但是要注意用法，否则编译器会报错。</li>
</ol>

<pre><code class="language-text">#define rac_weakify_(INDEX, CONTEXT, VAR) \
    CONTEXT __typeof__(VAR) metamacro_concat(VAR, _weak_) = (VAR);
</code></pre>

<p>回到metamacro_foreach_cxt的定义为</p>

<pre><code class="language-text">#define metamacro_foreach_cxt(MACRO, SEP, CONTEXT, ...) \
        metamacro_concat(metamacro_foreach_cxt, metamacro_argcount(__VA_ARGS__))(MACRO, SEP, CONTEXT, __VA_ARGS__)
</code></pre>

<p>本例中为</p>

<pre><code class="language-text">metamacro_concat(metamacro_foreach_cxt, metamacro_argcount(__VA_ARGS__))(MACRO, SEP, CONTEXT, __VA_ARGS__)

--&gt; metamacro_argcount(__VA_ARGS__)的结果为1.
--&gt; metamacro_foreach_cxt1(MACRO, SEP, CONTEXT, __VA_ARGS__)
--&gt; metamacro_foreach_cxt1(rac_weakify_, SEP, __weak, self)
</code></pre>

<p>metamacro_foreach_cxt1的定义为</p>

<pre><code class="language-text">#define metamacro_foreach_cxt1(MACRO, SEP, CONTEXT, _0) MACRO(0, CONTEXT, _0)
则
metamacro_foreach_cxt1(rac_weakify_, SEP, __weak, self)
--&gt; rac_weakify_(0,__weak,self)
</code></pre>

<p>rac_weakify_定义为</p>

<pre><code class="language-text">#define rac_weakify_(INDEX, CONTEXT, VAR) \
    CONTEXT __typeof__(VAR) metamacro_concat(VAR, _weak_) = (VAR);
--&gt; __weak __typeof__(self) self_weak_ = (self);
</code></pre>

<p>至此，我们看到了@weakify的作用，其将传入的参数，自动创建一个__weak的对象。<br/>
TODO：</p>

<ol>
<li> 多参数传入的解析过程</li>
<li>@autorelasepool并没有纳入任何东西。</li>
<li>莫名其妙的连续两个逗号。<br/>
我们继续来看一下，当weakify传入多个参数，如两个参数的时候，RAC是怎么处理的。<br/>
当weakify传入两个参数的时候，宏定义展开，某个阶段为
<code>metamacro_foreach_cxt2(rac_weakify_, SEP, __weak, __VA_ARGS__)</code>
其中__VA_ARGS__是两个参数，假设为self，和self.button
<code>metamacro_foreach_cxt2</code>的定义为</li>
</ol>

<pre><code class="language-text">#define metamacro_foreach_cxt2(MACRO, SEP, CONTEXT, _0, _1) \
    metamacro_foreach_cxt1(MACRO, SEP, CONTEXT, _0) \
    SEP \
    MACRO(1, CONTEXT, _1)
--&gt; metamacro_foreach_cxt2(rac_weakify_, SEP, __weak, self, self.button)
--&gt; 展开得 metamacro_foreach_cxt1(rac_weakify_, SEP, __weak, self) \
    SEP \
    rac_weakify_(1, __weak, self.button)
--&gt; __weak __typeof__(self) self_weak_ = (self); __weak __typeof__(self.button) self.button_weak_ = (self.button);
</code></pre>

<p>6.2 @strongify</p>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Realm学习笔记]]></title>
    <link href="https://nimitz871016.github.io/15216334372729.html"/>
    <updated>2018-03-21T19:57:17+08:00</updated>
    <id>https://nimitz871016.github.io/15216334372729.html</id>
    <content type="html"><![CDATA[
<ul>
<li>Useful文章</li>
</ul>

<p><a href="https://www.jianshu.com/p/6704afc62d6c">https://www.jianshu.com/p/6704afc62d6c</a></p>

<p><a href="https://academy.realm.io/cn/posts/jp-simard-realm-core-database-engine/">Realm 核心数据库引擎探秘</a></p>

<h2 id="toc_0">1. 模型定义</h2>

<p>参照官网的demo，建立如下两个类：</p>

<pre><code class="language-text">@interface Dog : RLMObject
@property NSString *name;
@property NSData   *picture;
@property NSInteger age;
@end
@implementation Dog
@end
RLM_ARRAY_TYPE(Dog)
@interface Person : RLMObject
@property NSString             *name;
@property RLMArray&lt;Dog *&gt;&lt;Dog&gt; *dogs;
@end
@implementation Person
@end
</code></pre>

<pre><code class="language-text">tips

1. 需要继承RLMObject
2. 所有的属性都不需要写任何的描述符，原子性，strong, assign等
3. Array类型需要用RLMArray
4. 使用Array 需要定义 RLM_ARRAY_TYPE , 这个定义放在类前类后都可以。
</code></pre>

<h2 id="toc_1">2.CRUD</h2>

<p>Realm采用了MVCC设计架构，因此读写操作是不互斥的。但是写操作最好在一个Seperate thread中执行，否则会降低效率。</p>

<h2 id="toc_2">2.1 创建（Create）</h2>

<pre><code class="language-text">// (1) Create a Dog object and then set its properties
    Dog *myDog = [[Dog alloc] init];myDog.name = @&quot;Rex&quot;;myDog.age = 10;
// (2) Create a Dog object from a dictionary
    Dog *myOtherDog = [[Dog alloc] initWithValue:@{@&quot;name&quot; : @&quot;Pluto&quot;, @&quot;age&quot; : @3}];
// (3) Create a Dog object from an array
    Dog *myThirdDog = [[Dog alloc] initWithValue:@[@&quot;Pluto&quot;, @3]];
</code></pre>

<p>写入数据库</p>

<pre><code class="language-text">    // Get the default Realm
    RLMRealm *realm = [RLMRealm defaultRealm];
    // You only need to do this once (per thread)
    
    // Add to Realm with transaction
    [realm beginWriteTransaction];
    [realm addObject:province];
    [realm commitWriteTransaction];

</code></pre>

<h2 id="toc_3">2.2 查询（Retrieve）</h2>

<pre><code class="language-text">// 使用断言字符串查询
    RLMResults&lt;ProvinceEntity *&gt; *provinceArray = [ProvinceEntity objectsWhere:@&quot;shortName = &#39;江苏&#39;&quot;];
    // 使用 NSPredicate 查询
    NSPredicate *pred = [NSPredicate predicateWithFormat:@&quot;shortName = &#39;江苏&#39;&quot;];
    provinceArray = [ProvinceEntity objectsWithPredicate:pred];

</code></pre>

<p>如果有多条件的话，可以用and，也可以分布查询，从查询结果中再做查询，支持链式查询：</p>

<pre><code class="language-text">RLMResults&lt;Dog *&gt; *tanDogs = [Dog objectsWhere:@&quot;color = &#39;棕黄色&#39;&quot;];
RLMResults&lt;Dog *&gt; *tanDogsWithBNames = [tanDogs objectsWhere:@&quot;name BEGINSWITH &#39;大&#39;&quot;];
</code></pre>

<p>RLMResults允许您指定一个排序标准，从而可以根据一个或多个属性进行排序。比如说，下列代码将上面例子中返回的狗狗根据名字升序进行排序：</p>

<pre><code class="language-text">// 排序名字以“大”开头的棕黄色狗狗
RLMResults&lt;Dog *&gt; *sortedDogs = [[Dog objectsWhere:@&quot;color = &#39;棕黄色&#39; AND name BEGINSWITH &#39;大&#39;&quot;] sortedResultsUsingProperty:@&quot;name&quot; ascending:YES];
</code></pre>

<h2 id="toc_4">2.3 更新（Update）</h2>

<ul>
<li>你可以找到具体的一条数据然后去更新：</li>
</ul>

<pre><code class="language-text">    RLMResults&lt;ProvinceEntity *&gt;* provinceArray=[ProvinceEntity allObjects];
    [[RLMRealm defaultRealm] transactionWithBlock:^{
        ProvinceEntity *province=[provinceArray firstObject];
        province.shortName=@&quot;浙江&quot;;
    }];
</code></pre>

<ul>
<li>你也可以设置一个主键，根据主键去更新，更新需要拥有一个主键---Primary Keys：</li>
</ul>

<pre><code class="language-text">// Creating a book with the same primary key as a previously saved 
bookBook *cheeseBook = [[Book alloc] init];
cheeseBook.title = @&quot;Cheese recipes&quot;;
cheeseBook.price = @9000;
cheeseBook.id = @1;
// Updating book with id = 1
[realm beginWriteTransaction];
[realm addOrUpdateObject:cheeseBook];
[realm commitWriteTransaction];
</code></pre>

<h2 id="toc_5">2.4 删除(Delete)</h2>

<ul>
<li>单条记录删除</li>
</ul>

<pre><code class="language-text">// Delete an object with a transaction
[realm beginWriteTransaction];
[realm deleteObject:cheeseBook];
[realm commitWriteTransaction];
</code></pre>

<ul>
<li>多条记录删除</li>
</ul>

<pre><code class="language-text">    [[RLMRealm defaultRealm] transactionWithBlock:^{
        [[RLMRealm defaultRealm] deleteObjects:result];
    }];
</code></pre>

<ul>
<li>全部删除：</li>
</ul>

<pre><code class="language-text">// Delete an object with a transaction
    [[RLMRealm defaultRealm] transactionWithBlock:^{
        [[RLMRealm defaultRealm] deleteAllObjects];
    }];
</code></pre>

<h2 id="toc_6">3.进阶使用</h2>

<ul>
<li>非空字段(Required properties)</li>
</ul>

<p>By default, NSString *, NSData *, and NSDate * properties allow you to set them to nil. If you want to require that a value be present, override the +requiredProperties method on your RLMObject subclass.</p>

<p>For example, with the following model definition, trying to set the person’s name to nil will throw an exception, but setting their birthday to nil is allowed:</p>

<pre><code class="language-text">@interface Person : RLMObject
@property NSString *name;
@property NSDate *birthday;
@end

@implementation Person
+ (NSArray *)requiredProperties {
    return @[@&quot;name&quot;];
}
@end
</code></pre>

<p>例外：RLMObject subclass properties always can be nil, and thus cannot be included in requiredProperties. and RLMArray does not support storing nil.</p>

<ul>
<li>主键</li>
</ul>

<p>Override +primaryKey to set the model’s primary key. Declaring a primary key allows objects to be looked up and updated efficiently and enforces uniqueness for each value. Once an object with a primary key is added to a Realm, the primary key cannot be changed.</p>

<pre><code class="language-text">@interface Person : RLMObject
@property NSInteger id;
@property NSString *name;
@end

@implementation Person
+ (NSString *)primaryKey {
    return @&quot;id&quot;;
}
@end
</code></pre>

<ul>
<li>索引字段</li>
</ul>

<p>To index a property, override +indexedProperties. Like primary keys, indexes make writes slightly slower, but makes queries using comparison operators faster. (It also makes your Realm file slightly larger, to store the index.) It’s best to only add indexes when you’re optimizing the read performance for specific situations.</p>

<pre><code class="language-text">@interface Book : RLMObject
@property float price;
@property NSString *title;
@end

@implementation Book
+ (NSArray *)indexedProperties {
    return @[@&quot;title&quot;];
}
@end
</code></pre>

<ul>
<li>忽略字段</li>
</ul>

<p>If you don’t want to save a field in your model to its Realm, override +ignoredProperties. Realm won’t interfere with the regular operation of these properties; they’ll be backed by ivars, and you can freely override their setters and getters.</p>

<pre><code class="language-text">@interface Person : RLMObject
@property NSInteger tmpID;
@property (readonly) NSString *name; // read-only properties are automatically ignored
@property NSString *firstName;
@property NSString *lastName;
@end

@implementation Person
+ (NSArray *)ignoredProperties {
    return @[@&quot;tmpID&quot;];
}
- (NSString *)name {
    return [NSString stringWithFormat:@&quot;%@ %@&quot;, self.firstName, self.lastName];
}
@end
</code></pre>

<ul>
<li>默认值</li>
</ul>

<p>Override +defaultPropertyValues to provide default values every time an object is created.</p>

<pre><code class="language-text">@interface Book : RLMObject
@property float price;
@property NSString *title;
@end

@implementation Book
+ (NSDictionary *)defaultPropertyValues {
    return @{@&quot;price&quot; : @0, @&quot;title&quot;: @&quot;&quot;};
}
@end
</code></pre>

<h2 id="toc_7">4. 特性</h2>

<h2 id="toc_8">5. 疑问</h2>

<p>为什么要这样直接继承不可以吗？是不支持吗？</p>

<pre><code class="language-text">// Base Model
@interface Animal : RLMObject
@property NSInteger age;
@end
@implementation Animal
@end

// Models composed with Animal
@interface Duck : RLMObject
@property Animal *animal;
@property NSString *name;
@end
@implementation Duck
@end

@interface Frog : RLMObject
@property Animal *animal;
@property NSDate *dateProp;
@end
@implementation Frog
@end

// Usage
Duck *duck =  [[Duck alloc] initWithValue:@{@&quot;animal&quot; : @{@&quot;age&quot; : @(3)}, @&quot;name&quot; : @&quot;Gustav&quot; }];
</code></pre>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[一道LeetCode线程题引出Java线程协作的经典案例]]></title>
    <link href="https://nimitz871016.github.io/15819118469978.html"/>
    <updated>2020-02-17T11:57:26+08:00</updated>
    <id>https://nimitz871016.github.io/15819118469978.html</id>
    <content type="html"><![CDATA[
<h2 id="toc_0">1. 题目</h2>

<p><strong>1115. 交替打印FooBar</strong></p>

<p>我们提供一个类：</p>

<pre><code class="language-text">class FooBar {
  public void foo() {
    for (int i = 0; i &lt; n; i++) {
      print(&quot;foo&quot;);
    }
  }

  public void bar() {
    for (int i = 0; i &lt; n; i++) {
      print(&quot;bar&quot;);
    }
  }
}
</code></pre>

<p>两个不同的线程将会共用一个 FooBar 实例。其中一个线程将会调用 foo() 方法，另一个线程将会调用 bar() 方法。</p>

<p>请设计修改程序，以确保 &quot;foobar&quot; 被输出 n 次。</p>

<p> <br/>
示例 1:</p>

<pre><code class="language-text">输入: n = 1
输出: &quot;foobar&quot;
解释: 这里有两个线程被异步启动。其中一个调用 foo() 方法, 另一个调用 bar() 方法，&quot;foobar&quot; 将被输出一次。
</code></pre>

<p>示例 2:</p>

<pre><code class="language-text">输入: n = 2
输出: &quot;foobarfoobar&quot;
解释: &quot;foobar&quot; 将被输出两次。
</code></pre>

<p>来源：力扣（LeetCode）<br/>
链接：<a href="https://leetcode-cn.com/problems/print-foobar-alternately">https://leetcode-cn.com/problems/print-foobar-alternately</a><br/>
著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。</p>

<h2 id="toc_1">2. 分析</h2>

<p>本题有两个要求</p>

<ol>
<li>顺序性，即foo要在bar之前打印，需要考虑先执行print bar的情况。</li>
<li>交替性，foo和bar需要轮流打印。</li>
</ol>

<h2 id="toc_2">2.1. 方案一（基于volatile）</h2>

<p>用一个变量来标记当前打印的是foo还是bar。这样就知道下一个操作需要打印foo还是bar。这个变量需要在线程间进行共享。共享没有问题，FooBar内的变量对同一个对象是可以访问的。但是需要能够及时同步。因此我们需要一个volatile变量。</p>

<p><strong>1.0版本:</strong></p>

<pre><code class="language-text">class FooBar {
    private int n;

    volatile boolean flag = true;

    public FooBar(int n) {
        this.n = n;
    }

    public void foo(Runnable printFoo) throws InterruptedException {
        for (int i = 0; i &lt; n; i++) {
            while (!flag){
            }
                // printFoo.run() outputs &quot;foo&quot;. Do not change or remove this line.
            printFoo.run();
            flag = false;
        }
    }

    public void bar(Runnable printBar) throws InterruptedException {

        for (int i = 0; i &lt; n; i++) {
                // printBar.run() outputs &quot;bar&quot;. Do not change or remove this line.
            while (flag){
            }
            printBar.run();
            flag = true;
        }
    }
}
</code></pre>

<p><img src="media/15819118469978/15819276109011.jpg" alt="" style="width:1142px;"/></p>

<p>提交，超时了。为啥呢？</p>

<p>考虑CPU单核的情况，<code>while (flag){}</code>如果是bar线程先运行，将会不停执行while。foo线程无法抢占时间片，自然无法开始第一步print foo了。在多核环境下，虽然不会造成另一线程无法抢占时间片的问题，但是while循环是很耗时的，占用大量CPU资源，这也会使得运行时间变长而超时。</p>

<p>基于这样的分析，修改一下，增加Thread.sleep()，每次循环的时候，休眠一会儿。</p>

<p><strong>2.0版本:</strong></p>

<pre><code class="language-text">class FooBar {
    private int n;

    volatile boolean flag = true;

    public FooBar(int n) {
        this.n = n;
    }

    public void foo(Runnable printFoo) throws InterruptedException {
        for (int i = 0; i &lt; n; i++) {
            while (!flag){
                Thread.sleep(20);
            }
                // printFoo.run() outputs &quot;foo&quot;. Do not change or remove this line.
            printFoo.run();
            flag = false;
        }
    }

    public void bar(Runnable printBar) throws InterruptedException {

        for (int i = 0; i &lt; n; i++) {
                // printBar.run() outputs &quot;bar&quot;. Do not change or remove this line.
            while (flag){
                Thread.sleep(20);
            }
            printBar.run();
            flag = true;
        }
    }
}
</code></pre>

<p>提交，进步了一点。还是<strong>超时</strong>。<br/>
<img src="media/15819118469978/15819276627967.jpg" alt="" style="width:1139px;"/></p>

<p>我们已经把休眠时间调整的很小了（20ms），希望程序可以快点切换到下一个打印。我们或许可以通过继续把休眠时间调整的更小来通过这道题，但是我们有一个更好的方法。<code>Thread.yield()</code></p>

<p><strong>3.0版本来了:</strong></p>

<pre><code class="language-text">class FooBar {
    private int n;

    volatile boolean flag = true;

    public FooBar(int n) {
        this.n = n;
    }

    public void foo(Runnable printFoo) throws InterruptedException {
        for (int i = 0; i &lt; n; i++) {
            while (!flag){
                Thread.yield();
            }
                // printFoo.run() outputs &quot;foo&quot;. Do not change or remove this line.
            printFoo.run();
            flag = false;
        }
    }

    public void bar(Runnable printBar) throws InterruptedException {

        for (int i = 0; i &lt; n; i++) {
                // printBar.run() outputs &quot;bar&quot;. Do not change or remove this line.
            while (flag){
                Thread.yield();
            }
            printBar.run();
            flag = true;
        }
    }
}
</code></pre>

<p><img src="media/15819118469978/15819289512587.jpg" alt="" style="width:1159px;"/><br/>
通过了！！！</p>

<pre><code class="language-text">摘抄自LeetCode评论：https://leetcode-cn.com/problems/print-foobar-alternately/solution/xian-cheng-ping-zhang-de-wen-ti-yi-ban-you-san-cho/
while循环是比较耗费性能的，可能会导致执行结果超时。可以通过Thread.yield进一步控制线程的执行，而非比较粗力度的循环。当某个线程调用yield()方法时，就会从运行状态转换到就绪状态后，CPU从就绪状态线程队列中只会选择与该线程优先级相同或者更高优先级的线程去执行。总之加上Thread.yield性能会更高一点，因此用时会更少
</code></pre>

<p>什么是Thread.yield()?</p>

<p>摘抄自：<a href="https://www.cnblogs.com/java-spring/p/8309931.html">https://www.cnblogs.com/java-spring/p/8309931.html</a></p>

<p>Java线程中的Thread.yield( )方法，译为线程让步。顾名思义，就是说当一个线程使用了这个方法之后，它就会把自己CPU执行的时间让掉，<br/>
让自己或者其它的线程运行，注意是让自己或者其他线程运行，并不是单纯的让给其他线程。<br/>
yield()的作用是让步。它能让当前线程由“运行状态”进入到“就绪状态”，从而让其它具有相同优先级的等待线程获取执行权；但是，并不能保证在当前线程调用yield()之后，其它具有相同优先级的线程就一定能获得执行权；也有可能是当前线程又进入到“运行状态”继续运行！<br/>
举个例子：一帮朋友在排队上公交车，轮到Yield的时候，他突然说：我不想先上去了，咱们大家来竞赛上公交车。然后所有人就一块冲向公交车，<br/>
有可能是其他人先上车了，也有可能是Yield先上车了。<br/>
但是线程是有优先级的，优先级越高的人，就一定能第一个上车吗？这是不一定的，优先级高的人仅仅只是第一个上车的概率大了一点而已，<br/>
最终第一个上车的，也有可能是优先级最低的人。并且所谓的优先级执行，是在大量执行次数中才能体现出来的。</p>

<h2 id="toc_3">2.2 方案二 Semaphore</h2>

<p>Semaphore<br/>
<a href="https://blog.csdn.net/hanchao5272/article/details/79780045">https://blog.csdn.net/hanchao5272/article/details/79780045</a></p>

<p>基于Semaphore的代码如下:</p>

<pre><code class="language-text">class FooBar {

    private int n;

    private Semaphore semaphore = new Semaphore(1);

    private volatile boolean foo = false;

    public FooBar(int n) {
        this.n = n;
    }

    public void foo(Runnable printFoo) throws InterruptedException {

        for (int i = 0; i &lt; n; i++) {
            semaphore.acquire();
            // printFoo.run() outputs &quot;foo&quot;. Do not change or remove this line.
            printFoo.run();
            foo = true;
        }
    }

    public void bar(Runnable printBar) throws InterruptedException {

        for (int i = 0; i &lt; n; i++) {
            while (!foo) {
            }
            // printBar.run() outputs &quot;bar&quot;. Do not change or remove this line.
            printBar.run();
            foo = false;
            semaphore.release();
        }
    }
}

作者：san-mu-32
链接：https://leetcode-cn.com/problems/print-foobar-alternately/solution/tong-guo-yi-ge-xin-hao-liang-kong-zhi-foohe-barde-/
来源：力扣（LeetCode）
著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
</code></pre>

<p><img src="media/15819118469978/15819321426228.jpg" alt="" style="width:759px;"/></p>

<p>涉及多线程，运行时间并不稳定。和方案一类似，在while循环中加入Thread.yield()，速度有一定提升。<br/>
<img src="media/15819118469978/15819322651608.jpg" alt="" style="width:716px;"/></p>

<h2 id="toc_4">2.3. 方案三 notify &amp;&amp; wait</h2>

<p><a href="https://www.jianshu.com/p/1dafbf42cc54">https://www.jianshu.com/p/1dafbf42cc54</a></p>

<pre><code class="language-text">class FooBar {

    private int              n;
    private volatile boolean isFoo;

    public FooBar(int n) {
        this.n = n;
    }

    public synchronized void foo(Runnable printFoo) throws InterruptedException {

        for (int i = 0; i &lt; n; i++) {
            //            synchronized (lock) {
            // printFoo.run() outputs &quot;foo&quot;. Do not change or remove this line.
            printFoo.run();
            isFoo = true;
            this.notify();
            if (i &lt; n - 1) {
                this.wait();
            }
            //            }
        }
    }

    public synchronized void bar(Runnable printBar) throws InterruptedException {
        if (!isFoo) {
            this.wait();
        }
        for (int i = 0; i &lt; n; i++) {
            //            synchronized (lock) {
            // printBar.run() outputs &quot;bar&quot;. Do not change or remove this line.
            printBar.run();
            this.notify();
            if (i &lt; n - 1) {
                this.wait();
            }
            //            }
        }
    }
}
</code></pre>

<p><img src="media/15819118469978/15820959963519.jpg" alt="" style="width:747px;"/><br/>
运行时间不稳定，应该是LeetCode的问题。</p>

<h2 id="toc_5">2.4. 方案四 CyclicBarrier</h2>

<p><a href="https://www.jianshu.com/p/333fd8faa56e">https://www.jianshu.com/p/333fd8faa56e</a></p>

<pre><code class="language-text">class FooBar {
    private int n;

    public FooBar(int n) {
        this.n = n;
    }

    CyclicBarrier cb = new CyclicBarrier(2);
    volatile boolean fin = true;

    public void foo(Runnable printFoo) throws InterruptedException {
        for (int i = 0; i &lt; n; i++) {
            while(!fin);
            printFoo.run();
            fin = false;
            try {
        cb.await();
        } catch (BrokenBarrierException e) {
        }
        }
    }

    public void bar(Runnable printBar) throws InterruptedException {
        for (int i = 0; i &lt; n; i++) {
            try {
        cb.await();
        } catch (BrokenBarrierException e) {
        }
            printBar.run();
            fin = true;
        }
    }
}

作者：KevinBauer
链接：https://leetcode-cn.com/problems/print-foobar-alternately/solution/java-bing-fa-gong-ju-lei-da-lian-bing-by-kevinbaue/
来源：力扣（LeetCode）
著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
</code></pre>

<h2 id="toc_6">2.5. 方案五 CyclicBarrier + CountdownLatch</h2>

<p>CyclicBarrier用于保证每一轮的foobar的打印。CountdownLatch用于保证单轮内，先打印foo，再打印bar。</p>

<pre><code class="language-text">import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
class FooBar {
    private int n;
    private CountDownLatch a;
    private CyclicBarrier barrier;// 使用CyclicBarrier保证任务按组执行
    public FooBar(int n) {
        this.n = n;
        a = new CountDownLatch(1);
        barrier = new CyclicBarrier(2);// 保证每组内有两个任务
    }

    public void foo(Runnable printFoo) throws InterruptedException {

        try {
            for (int i = 0; i &lt; n; i++) {
                printFoo.run();
                a.countDown();// printFoo方法完成调用countDown
                barrier.await();// 等待printBar方法执行完成
            }
        } catch(Exception e) {}
    }

    public void bar(Runnable printBar) throws InterruptedException {

        try {
            for (int i = 0; i &lt; n; i++) {
                a.await();// 等待printFoo方法先执行
                printBar.run();
                a = new CountDownLatch(1); // 保证下一次依旧等待printFoo方法先执行
                barrier.await();// 等待printFoo方法执行完成
            }
        } catch(Exception e) {}
    }
}

作者：bonaluo
链接：https://leetcode-cn.com/problems/print-foobar-alternately/solution/javashi-yong-yi-ge-countdownlatchhe-yi-ge-cyclicba/
来源：力扣（LeetCode）
著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
</code></pre>

<h2 id="toc_7">3. 总结</h2>

<p>本题需要解决两个问题。</p>

<ol>
<li>两个线程必须先后执行，</li>
<li>foo线程必须保证先执行。</li>
</ol>

<p>为了解决这两个问题，5个方案选择了不同的方案组合。主要分为无锁和有锁两种方案。</p>

<p>为了解决foo线程先执行的问题。有使用volatile变量和CountdownLatch两种方法。volatile变量使用的是无锁的方案。通过一个死循环，组织bar线程先运行。优点是可以快速感知状态变换，无需线程切换。缺点是资源消耗大，需要使用Thread.yield()。否则会超时。CountdownLatch采用的是有锁的方案，因此会有线程的切换，单不会大量占用系统资源。在线程占用时间长的场景体验更佳。</p>

<p>为了让两个线程先后执行，需要在foo线程执行后挂起线程，让bar线程运行。在bar线程运行后，再让foo线程执行。无锁方案，继续用volatile变量即可。有锁方案则可以有几种选择。Semaphore，CyclicBarrier，notify&amp;wait，Lock。</p>

<h2 id="toc_8">4. 引申</h2>

<p>　　在 单核 / 单CPU 的系统上使用 自旋锁 是没有意义的，因为它就一个运行线程/核心，你占着不放，那么其他线程将得不到运行，其他线程得不到运行，这个锁就不能被解锁。换句话说，在 单核 / 单CPU 系统使用 自旋锁，除了浪费点时间外没有一点好处。这时如果让这个线程（记为线程A）休眠，其他线程就得以运行，然后就可能会解锁这个 自旋锁，线程A就可能在重新被唤醒后，如愿以偿的持有锁。</p>

<p>　　在 多核 / 多CPU 的系统上，特别是大量的线程只会短时间的持有锁的时候，这时如果使用 互斥锁，在使线程睡眠和唤醒上浪费大量的时间，也许会显著降低程序的运行性能。使用 自旋锁，线程可以充分利用系统调度程序分配的时间片(经常阻塞很短的时间，不用休眠，然后马上继续它们后面的工作了)，以达到更高的处理能力和吞吐量。</p>

<p><a href="https://www.cnblogs.com/shines77/p/4198046.html">https://www.cnblogs.com/shines77/p/4198046.html</a></p>

]]></content>
  </entry>
  
</feed>
