<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>呦呵丶晓晓的博客</title>
  
  <subtitle>一只贪吃嗜睡的呆萌程序员</subtitle>
  <link href="https://newgr8player.top/atom.xml" rel="self"/>
  
  <link href="https://newgr8player.top/"/>
  <updated>2024-12-13T07:02:02.000Z</updated>
  <id>https://newgr8player.top/</id>
  
  <author>
    <name>呦呵丶晓晓</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>LeetCode题解 - 01.两数之和</title>
    <link href="https://newgr8player.top/posts/LeetCode%E9%A2%98%E8%A7%A3-01-%E4%B8%A4%E6%95%B0%E4%B9%8B%E5%92%8C/"/>
    <id>https://newgr8player.top/posts/LeetCode%E9%A2%98%E8%A7%A3-01-%E4%B8%A4%E6%95%B0%E4%B9%8B%E5%92%8C/</id>
    <published>2020-11-27T04:05:13.000Z</published>
    <updated>2024-12-13T07:02:02.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="题目"><a href="#题目" class="headerlink" title="题目"></a>题目</h1><p><a href="https://leetcode-cn.com/problems/two-sum">1.两数之和</a><br>难度：<code>简单</code></p><p>给定一个整数数组 <code>nums</code> 和一个目标值 <code>target</code>，请你在该数组中找出和为目标值的那 <code>两个</code> 整数，并返回他们的数组下标。</p><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></pre></td><td class="code"><pre><span class="line">示例:</span><br><span class="line"></span><br><span class="line">给定 nums = [2, 7, 11, 15], target = 9</span><br><span class="line"></span><br><span class="line">因为 nums[0] + nums[1] = 2 + 7 = 9</span><br><span class="line">所以返回 [0, 1]</span><br></pre></td></tr></table></figure><h1 id="题解"><a href="#题解" class="headerlink" title="题解"></a>题解</h1><h2 id="暴力法"><a href="#暴力法" class="headerlink" title="暴力法"></a>暴力法</h2><blockquote><p>确定第一个元素<code>i</code>，然后搜索其后的元素<code>j</code>，当满足条件<code>nums[i] + nums[j] == target</code>时，返回<code>[i,j]</code></p></blockquote><ul><li>时间复杂度：O(n^2^)</li><li>空间复杂度：O(1)</li></ul><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></pre></td><td class="code"><pre><span class="line">class Solution:</span><br><span class="line">    def twoSum(self, nums: List[int], target: int) -&gt; List[int]:</span><br><span class="line">        length = len(nums)</span><br><span class="line">        for i in range(length):</span><br><span class="line">            for j in range(i + 1, length):</span><br><span class="line">                if nums[i] + nums[j] == target:</span><br><span class="line">                    return [i, j]</span><br></pre></td></tr></table></figure><h2 id="哈希表"><a href="#哈希表" class="headerlink" title="哈希表"></a>哈希表</h2><blockquote><p>将元素放入<code>HashMap</code>中，使用元素值作为Key，元素的索引作为Value<br>查找时当使用<code>target - num[i]</code>为Key的元素存在则返回<code>[HashMap[target - num[i]],num[i]]</code></p></blockquote><ul><li>时间复杂度 O(n)</li><li>空间复杂度 O(n)</li></ul><blockquote><p>使用测试样例测试返回值为<code>[1,0]</code>,与给定结果的<code>[0,1]</code>顺序不符，但也能通过。</p></blockquote><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">class Solution:</span><br><span class="line">    def twoSum(self, nums: List[int], target: int) -&gt; List[int]:</span><br><span class="line">        table = dict()</span><br><span class="line">        for i in range(len(nums)):</span><br><span class="line">            n = nums[i]</span><br><span class="line">            tmp = target - n</span><br><span class="line">            if tmp in table:</span><br><span class="line">                return [i, table[tmp]]</span><br><span class="line">            else:</span><br><span class="line">                table[n] = i</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;题目&quot;&gt;&lt;a href=&quot;#题目&quot; class=&quot;headerlink&quot; title=&quot;题目&quot;&gt;&lt;/a&gt;题目&lt;/h1&gt;&lt;p&gt;&lt;a href=&quot;https://leetcode-cn.com/problems/two-sum&quot;&gt;1.两数之和&lt;/a&gt;&lt;br&gt;难度：&lt;c</summary>
      
    
    
    
    <category term="LeetCode" scheme="https://newgr8player.top/categories/LeetCode/"/>
    
    
    <category term="LeetCode" scheme="https://newgr8player.top/tags/LeetCode/"/>
    
    <category term="算法" scheme="https://newgr8player.top/tags/%E7%AE%97%E6%B3%95/"/>
    
  </entry>
  
  <entry>
    <title>Java面试（阿里P6）</title>
    <link href="https://newgr8player.top/posts/Java%E9%9D%A2%E8%AF%95%EF%BC%88%E9%98%BF%E9%87%8CP6%EF%BC%89/"/>
    <id>https://newgr8player.top/posts/Java%E9%9D%A2%E8%AF%95%EF%BC%88%E9%98%BF%E9%87%8CP6%EF%BC%89/</id>
    <published>2019-09-07T00:38:54.000Z</published>
    <updated>2019-09-08T09:44:00.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="一面-技术面"><a href="#一面-技术面" class="headerlink" title="一面(技术面)"></a>一面(技术面)</h1><h2 id="自我介绍和项目"><a href="#自我介绍和项目" class="headerlink" title="自我介绍和项目"></a><del>自我介绍和项目</del></h2><blockquote><p>这个因人而异，但自我介绍和项目多说一些简历上没有的。</p></blockquote><h2 id="Java的内存分区"><a href="#Java的内存分区" class="headerlink" title="Java的内存分区"></a>Java的内存分区</h2><blockquote><p>Java程序是交由JVM执行的，所以Java内存区域划分的时候事实上是指JVM区域划分。</p></blockquote><h3 id="执行过程"><a href="#执行过程" class="headerlink" title="执行过程"></a>执行过程</h3><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/09/07/a7114520-d170-11e9-a9ea-05f2a361a2f9.png" alt="执行过程.png"></p><p>如图所示，首先Java源代码文件(.java后缀)会被Java编译器编译为字节码文件（.class后缀），然后由JVM中的类加载器加载各个类的字节码文件，加载完毕之后，交由JVM执行引擎执行。在整个程序执行过程中，JVM会用一段空间来存储程序执行期间需要用到的数据和相关信息，这段空间一般被称作Runtime Data Area（运行时数据区）也就是我们常常说的JVM内存。因此，在Java中我们常常说到的内存管理就是针对这段空间进行管理（如何分配和回收内存空间）。</p><h3 id="运行时数据区"><a href="#运行时数据区" class="headerlink" title="运行时数据区"></a>运行时数据区</h3><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/09/07/6bf10ce0-d171-11e9-a9ea-05f2a361a2f9.png" alt="Runtime Data Area.png"></p><p>根据《Java虚拟机规范》的规定，运行时数据区通常包括这几个部分：</p><ul><li>Java栈（VM Stack）</li><li>本地方法区（Native Method Stack）</li><li>程序计数器（Program Counter Register）</li><li>方法区（Method Area）</li><li>堆（Heap）</li></ul><p>如上图所示，JVM运行时数据区包括这五部分，在JVM规范中虽然规定了程序在执行期间运行时数据区应该包括这几部分，但是至于具体如何实现并没有做出规定，不同的虚拟机厂商可以有不同的实现方式。</p><h3 id="运行时数据区的每部分存储了那些数据？"><a href="#运行时数据区的每部分存储了那些数据？" class="headerlink" title="运行时数据区的每部分存储了那些数据？"></a>运行时数据区的每部分存储了那些数据？</h3><h4 id="程序计数器"><a href="#程序计数器" class="headerlink" title="程序计数器"></a>程序计数器</h4><p><code>程序计数器（Program Counter Regist）</code>也有称作为<code>PC寄存器</code>，在汇编语言中，程序计数器是指<code>CPU中的寄存器</code>，它保存的是程序当前执行的指令地址（也可以说是下一条指令的所在存储单元地址），当CPU需要指令时，需要从程序计数器中得到当前 执行的指令所在存储单元地址，然后根据得到的地址获取到指令，在得到指令后，程序计数器便会自动加1或者根据转移指针得到下一条指令的地址，如此循环，直至执行完所有指令。<br>虽然JVM中的程序计数器并不像汇编语言中的程序计数器一样是物理概念上的CUP寄存器，但是JVM中的程序计数器的功能跟汇编语言中的程序计数器的功能在<code>逻辑上是等同的</code>，也就是说是用来指示<code>执行哪条指令的</code>。<br>由于在JVM中，多线程是通过线程轮流切换来获得CPU执行时间的，因此，在任一具体时刻，一个CPU的内核只会执行一条线程中的指令，因此，为了能够使得每个线程都在线程切换后能够恢复在切换之前的程序执行位置，每个线程都需要有自己独立的程序计数器，并且不能互相被干扰，否则就会影响到程序的正常执行次序。因此，程序计数器是每个线程所私有的。<br>在JVM规范中规定，如果线程执行的是<code>非native方法</code>，则程序计数器中保存的是<code>当前需要执行的指令的地址</code>；如果线程执行的是<code>native方法</code>，则程序计数器中的值是<code>undefined</code>。<br>由于程序计数器中存储的数据所占空间的大小不会随程序的执行而发生改变，因此，对于程序计数器不会发生<code>内存溢出现象(OutOfMemory)</code>。</p><h4 id="Java栈"><a href="#Java栈" class="headerlink" title="Java栈"></a>Java栈</h4><p>Java栈也称作是<code>虚拟机栈（Java Vitual Machine Stack）</code>，也就是我们常常所说的栈，跟c语言的数据段中的栈类似。事实上，Java栈是Java方法执行的内存模型。<br>Java栈中存放的是一个个栈帧，每个栈帧对应着一个被调用的方法，在栈帧中包括:</p><ul><li>局部变量表（Local Variable）</li><li>操作数栈（Operaand Stack）</li><li>指向当前方法所属的类的运行时常量池的引用（Reference to runtime constant tool）</li><li>方法返回地址（Return Address）</li><li>一些额外的附加信息</li></ul><p>当线程执行一个方法时，就会随之创建一个对应的栈帧，并将建立的栈帧压栈。当方法执行完毕之后，便会将栈帧出栈。因此可知，线程当前执行的方法所对应的栈帧必定位于Java栈的顶部。讲到这里，大家就应该会明白为什么 在 使用 递归方法的时候容易导致栈内存溢出的现象了以及为什么栈区的空间不用程序员去管理了（当然在Java中，程序员基本不用关系到内存分配和释放的事情，因为Java有自己的垃圾回收机制），这部分空间的分配和释放都是由系统自动实施的。对于所有的程序设计语言来说，栈这部分空间对程序员来说是不透明的。下图表示了一个Java栈的模型：</p><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/09/07/b20c3a50-d177-11e9-a9ea-05f2a361a2f9.png" alt="image.png"></p><ul><li>局部变量表，顾名思义，就是用来存储放出方法中的局部变量（包括在方法中申明的非静态变量以及函数形参）。对于基本数据类型的变量，则直接存储他的值，对于应用类型的变量，存的是指向对象的应用。</li><li>操作栈帧，想必学过数据结构中的栈的朋友想必对表达式求值问题不会陌生，栈最典型的一个应用就是用来对表达式求值。想想一个线程执行方法的过程中，实际上就是不断执行语句的过程，而归根到底就是进行计算的过程。因此可以这么说，程序中的所有计算过程都是在借助于操作数栈来完成的。</li><li>指向运行时常量池的引用，因为在方法执行的过程中有可能需要用到类中的常量，所以必须要有一个引用指向运行时的常量池</li><li>方法返回地址，当一个方法执行完毕后，要返回之前调用它的地方，因此在栈帧中必须保存一个方法的返回地址。</li><li>虚拟机规范允许具体的虚拟机实现增加一些规范里没有描述的信息到栈帧中，例如与高度相关的信息，这部分信息完全取决于具体的虚拟机实现。在实际开发中，一般会把动态连接，方法返回地址与其它附加信息全部归为一类，称为栈帧信息。</li></ul><blockquote><p>由于每个线程执行正在执行的方法可能不同，因此每个线程都有一个Java栈，互不干扰。</p></blockquote><h4 id="本地方法栈"><a href="#本地方法栈" class="headerlink" title="本地方法栈"></a>本地方法栈</h4><p>本地方法栈与Java栈的作用和原理相似，区别只不过是Java栈是为执行Java方法服务的，而本地方法栈则是执行<code>本地方法（Native Method）</code>服务的，在JVM规范中，并没有对本地方法栈的具体实现方法以及数据结构做强制规定，虚拟机可以自由实现它，在HotSpot虚拟机中直接把本地方法栈和Java栈合二为一。</p><h4 id="堆"><a href="#堆" class="headerlink" title="堆"></a>堆</h4><p>在c语言中，堆这部分空间是唯一一个程序员管理的内存区域，程序员可以通过<code>malloc函数</code>和<code>free函数</code>在堆上申请和释放空间。Java中的堆是用来存储对象本身以及数组（数组引用是放在Java栈中的）。只不过和c语言不通，在Java中，程序员基本不关心空间释放的问题，Java的垃圾回收机制会自动进行处理，因此这部分空间也是Java垃圾收集器管理的主要区域。另外堆是被所有线程池共享的，在JVM中只有一个堆。</p><h4 id="方法区"><a href="#方法区" class="headerlink" title="方法区"></a>方法区</h4><p>方法区在JVM中也是一个非常重要的区域，它与堆一样，是被线程池共享的区域。在方法区中，存储每个类的信息（包括类的名称、方法信息、字段信息）静态变量、常量以及编译器变异后的的代码等。<br>在class文件中除了类的字段、方法、接口等描述信息外，还有一项是常量池，用来存储编译期间生成的字面量和符号引用。<br>在方法区中有一个非常重要的部分就是<code>运行时常量池</code>，它是每一个类或接口的常量池的运行时表示形式，在类和接口被加载到JVM后，对应的常量池就被创建出来。当然并非class文件常量池中的内容才能进入运行时常量池，在运行期间也可将新的常量放入运行时常量池中，比如String的intern方法。</p><blockquote><p>在JVM规范中，没有强制要求方法区必须实现垃圾回收机。</p></blockquote><h2 id="Java对象的回收方式，回收算法"><a href="#Java对象的回收方式，回收算法" class="headerlink" title="Java对象的回收方式，回收算法"></a>Java对象的回收方式，回收算法</h2><blockquote><p>其实问的就是垃圾收集策略与算法<br>程序计数器、虚拟机栈、本地方法栈随线程而生，也随线程而灭；栈帧随着方法的开始而入栈，随着方法的结束而出栈。这几个区域的内存分配和回收都具有确定性，在这几个区域内不需要过多考虑回收的问题，因为方法结束或者线程结束时，内存自然就跟随着回收了。<br>而对于 Java 堆和方法区，我们只有在程序运行期间才能知道会创建哪些对象，这部分内存的分配和回收都是动态的，垃圾收集器所关注的正是这部分内存。</p></blockquote><h3 id="判定对象是否存活"><a href="#判定对象是否存活" class="headerlink" title="判定对象是否存活"></a>判定对象是否存活</h3><blockquote><p>若一个对象不被任何对象或变量引用，那么它就是无效对象，需要被回收。</p></blockquote><h4 id="引用计数法"><a href="#引用计数法" class="headerlink" title="引用计数法"></a>引用计数法</h4><p>在对象头维护着一个 counter 计数器，对象被引用一次则计数器 +1；若引用失效则计数器 -1。当计数器为 0 时，就认为该对象无效了。</p><p>引用计数算法的实现简单，判定效率也很高，在大部分情况下它都是一个不错的算法。但是主流的 Java 虚拟机里没有选用引用计数算法来管理内存，主要是因为它很难解决对象之间循环引用的问题。</p><blockquote><p><strong>e.g.</strong> <code>objA</code> 和 <code>objB</code> 都有字段<code>instance</code>表示当前对象对其他对象的引用<br>objA.instance &#x3D; objB<br>objB.instance &#x3D; objA<br>由于它们互相引用着对方，导致它们的引用计数都不为 0，于是引用计数算法无法通知 GC 收集器回收它们。</p></blockquote><h4 id="可达性分析法"><a href="#可达性分析法" class="headerlink" title="可达性分析法"></a>可达性分析法</h4><p>所有和<code>GC Roots</code><strong>直接</strong>或<strong>间接</strong>关联的对象都是<strong>有效对象</strong>，和<code>GC Roots</code>没有关联的对象就是<strong>无效对象</strong>。</p><p><code>GC Roots</code>是指：</p><ul><li>Java 虚拟机栈（栈帧中的本地变量表）中引用的对象</li><li>本地方法栈中引用的对象</li><li>方法区中常量引用的对象</li><li>方法区中类静态属性引用的对象</li></ul><blockquote><p>GC Roots 并不包括堆中对象所引用的对象，这样就不会有循环引用的问题。</p></blockquote><h3 id="引用的种类"><a href="#引用的种类" class="headerlink" title="引用的种类"></a>引用的种类</h3><blockquote><p>判定对象是否存活与<code>引用</code>有关。在 JDK 1.2 以前，Java 中的引用定义很传统，一个对象只有被引用或者没有被引用两种状态，我们希望能描述这一类对象：当内存空间还足够时，则保留在内存中；如果内存空间在进行垃圾手收集后还是非常紧张，则可以抛弃这些对象。很多系统的缓存功能都符合这样的应用场景。<br>在 JDK 1.2 之后，Java 对引用的概念进行了扩充，将引用分为了以下四种。不同的引用类型，主要体现的是对象不同的可达性状态<code>reachable</code>和垃圾收集的影响。</p></blockquote><h4 id="强引用（Strong-Reference）"><a href="#强引用（Strong-Reference）" class="headerlink" title="强引用（Strong Reference）"></a>强引用（Strong Reference）</h4><p>类似 “Object obj &#x3D; new Object()” 这类的引用，就是强引用，只要强引用存在，垃圾收集器永远不会回收被引用的对象。但是，如果我们<strong>错误地保持了强引用</strong>，比如：赋值给了 static 变量，那么对象在很长一段时间内不会被回收，会产生内存泄漏。</p><h4 id="软引用（Soft-Reference）"><a href="#软引用（Soft-Reference）" class="headerlink" title="软引用（Soft Reference）"></a>软引用（Soft Reference）</h4><p>软引用是一种相对强引用弱化一些的引用，可以让对象豁免一些垃圾收集，只有当 JVM 认为内存不足时，才会去试图回收软引用指向的对象。JVM 会确保在抛出 OutOfMemoryError 之前，清理软引用指向的对象。软引用通常用来<strong>实现内存敏感的缓存</strong>，如果还有空闲内存，就可以暂时保留缓存，当内存不足时清理掉，这样就保证了使用缓存的同时，不会耗尽内存。</p><h4 id="弱引用（Weak-Reference）"><a href="#弱引用（Weak-Reference）" class="headerlink" title="弱引用（Weak Reference）"></a>弱引用（Weak Reference）</h4><p>弱引用的<strong>强度比软引用更弱</strong>一些。当 JVM 进行垃圾回收时，<strong>无论内存是否充足，都会回收</strong>只被弱引用关联的对象。</p><h4 id="虚引用（Phantom-Reference）"><a href="#虚引用（Phantom-Reference）" class="headerlink" title="虚引用（Phantom Reference）"></a>虚引用（Phantom Reference）</h4><p>虚引用也称幽灵引用或者幻影引用，它是<strong>最弱</strong>的一种引用关系。一个对象是否有虚引用的存在，完全不会对其生存时间构成影响。它仅仅是提供了一种确保对象被 finalize 以后，做某些事情的机制，比如，通常用来做所谓的 Post-Mortem 清理机制。</p><h3 id="回收堆中无效对象"><a href="#回收堆中无效对象" class="headerlink" title="回收堆中无效对象"></a>回收堆中无效对象</h3><p>对于可达性分析中不可达的对象，也并不是没有存活的可能。</p><h4 id="判定finalize-是否有必要执行"><a href="#判定finalize-是否有必要执行" class="headerlink" title="判定finalize()是否有必要执行"></a>判定<code>finalize()</code>是否有必要执行</h4><p>JVM 会判断此对象是否有必要执行 finalize() 方法，如果对象没有覆盖 finalize() 方法，或者 finalize() 方法已经被虚拟机调用过，那么视为“没有必要执行”。那么对象基本上就真的被回收了。</p><p>如果对象被判定为有必要执行<code>finalize()</code>方法，那么对象会被放入一个<code>F-Queue</code>队列中，虚拟机会以较低的优先级执行这些<code>finalize()</code>方法，但不会确保所有的<code>finalize()</code>方法都会执行结束。如果<code>finalize()</code>方法出现耗时操作，虚拟机就直接停止指向该方法，将对象清除。</p><h4 id="对象重生或死亡"><a href="#对象重生或死亡" class="headerlink" title="对象重生或死亡"></a>对象重生或死亡</h4><p>如果在执行<code>finalize()</code>方法时，将<code>this</code>赋给了某一个引用，那么该对象就重生了。如果没有，那么就会被垃圾收集器清除。</p><blockquote><p>任何一个对象的<code>finalize()</code>方法只会被系统<code>**自动**调用一次</code>，如果对象面临下一次回收，它的 finalize() 方法不会被再次执行，想继续在<code>finalize()</code>中自救就失效了。</p></blockquote><h3 id="回收方法区内存"><a href="#回收方法区内存" class="headerlink" title="回收方法区内存"></a>回收方法区内存</h3><p>方法区中存放生命周期较长的类信息、常量、静态变量，每次垃圾收集只有少量的垃圾被清除。方法区中主要清除两种垃圾：</p><ul><li>废弃常量</li><li>无用的类</li></ul><h4 id="判定废弃常量"><a href="#判定废弃常量" class="headerlink" title="判定废弃常量"></a>判定废弃常量</h4><p>只要常量池中的常量不被任何变量或对象引用，那么这些常量就会被清除掉。比如，一个字符串<code>&quot;bingo&quot;</code>进入了常量池，但是当前系统没有任何一个<code>String 对象</code>引用常量池中的<code>&quot;bingo&quot;</code>常量，也没有其它地方引用这个字面量，必要的话，<code>&quot;bingo&quot;常量</code>会被清理出常量池。</p><h4 id="判定无用的类"><a href="#判定无用的类" class="headerlink" title="判定无用的类"></a>判定无用的类</h4><p>判定一个类是否是“无用的类”，条件较为苛刻。</p><ul><li>该类的所有对象都已经被清除</li><li>加载该类的 ClassLoader 已经被回收</li><li>该类的 java.lang.Class 对象没有在任何地方被引用，无法在任何地方通过反射访问该类的方法。</li></ul><blockquote><p>一个类被虚拟机加载进方法区，那么在堆中就会有一个代表该类的对象：java.lang.Class。这个对象在类被加载进方法区时创建，在方法区该类被删除时清除。</p></blockquote><h3 id="垃圾收集算法"><a href="#垃圾收集算法" class="headerlink" title="垃圾收集算法"></a>垃圾收集算法</h3><p>学会了如何判定无效对象、无用类、废弃常量之后，剩余工作就是回收这些垃圾。常见的垃圾收集算法有以下几个：</p><h4 id="标记-清除算法"><a href="#标记-清除算法" class="headerlink" title="标记-清除算法"></a>标记-清除算法</h4><p><strong>标记</strong>的过程是：遍历所有的 <code>GC Roots</code>，然后将所有 <code>GC Roots</code> 可达的对象<strong>标记为存活的对象</strong>。<br><strong>清除</strong>的过程将遍历堆中所有的对象，将没有标记的对象全部清除掉。与此同时，清除那些被标记过的对象的标记，以便下次的垃圾回收。 </p><p>这种方法有两个<strong>不足</strong>：</p><ul><li>效率问题：标记和清除两个过程的效率都不高。</li><li>空间问题：标记清除之后会产生大量不连续的内存碎片，碎片太多可能导致以后需要分配较大对象时，无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。</li></ul><h4 id="复制算法（新生代）"><a href="#复制算法（新生代）" class="headerlink" title="复制算法（新生代）"></a>复制算法（新生代）</h4><p>为了解决效率问题，“复制”收集算法出现了。它将可用内存按容量划分为大小相等的两块，每次只使用其中的一块。当这一块内存用完，需要进行垃圾收集时，就将存活者的对象复制到另一块上面，然后将第一块内存全部清除。这种算法有优有劣：</p><ul><li>优点：不会有内存碎片的问题。</li><li>缺点：内存缩小为原来的一半，浪费空间。</li></ul><p>为了解决空间利用率问题，可以将内存分为三块： Eden、From Survivor、To Survivor，比例是 8:1:1，每次使用 Eden 和其中一块 Survivor。回收时，将 Eden 和 Survivor 中还存活的对象一次性复制到另外一块 Survivor 空间上，最后清理掉 Eden 和刚才使用的 Survivor 空间。这样只有 10% 的内存被浪费。</p><p>但是我们无法保证每次回收都只有不多于 10% 的对象存活，当 Survivor 空间不够，需要依赖其他内存（指老年代）进行分配担保。</p><h5 id="分配担保"><a href="#分配担保" class="headerlink" title="分配担保"></a>分配担保</h5><p>为对象分配内存空间时，如果 <code>Eden</code>+<code>Survivor</code> 中空闲区域无法装下该对象，会触发 MinorGC 进行垃圾收集。但如果 Minor GC 过后依然有超过 10% 的对象存活，这样存活的对象直接通过分配担保机制进入老年代，然后再将新对象存入<code>Eden</code>区。</p><h4 id="标记-整理算法（老年代）"><a href="#标记-整理算法（老年代）" class="headerlink" title="标记-整理算法（老年代）"></a>标记-整理算法（老年代）</h4><p><strong>标记</strong>：它的第一个阶段与<strong>标记&#x2F;清除算法</strong>是一模一样的，均是遍历 <code>GC Roots</code>，然后将存活的对象标记。<br><strong>整理</strong>：移动所有<strong>存活的对象</strong>，且按照内存地址次序依次排列，然后将末端内存地址以后的内存全部回收。因此，第二阶段才称为整理阶段。</p><p>这是一种老年代的垃圾收集算法。老年代的对象一般寿命比较长，因此每次垃圾回收会有大量对象存活，如果采用复制算法，每次需要复制大量存活的对象，效率很低。</p><h4 id="分代收集算法"><a href="#分代收集算法" class="headerlink" title="分代收集算法"></a>分代收集算法</h4><p>根据对象存活周期的不同，将内存划分为几块。一般是把 Java 堆分为新生代和老年代，针对各个年代的特点采用最适当的收集算法。  </p><ul><li>新生代：复制算法</li><li>老年代：标记-清除算法、标记-整理算法</li></ul><h2 id="CMS和G1了解么，CMS解决什么问题，说一下回收的过程。"><a href="#CMS和G1了解么，CMS解决什么问题，说一下回收的过程。" class="headerlink" title="CMS和G1了解么，CMS解决什么问题，说一下回收的过程。"></a>CMS和G1了解么，CMS解决什么问题，说一下回收的过程。</h2><blockquote><p>这个就分别说一下CMS(Concurrent Mark-Sweep)和G1吧~</p></blockquote><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/09/07/d7a34400-d179-11e9-a9ea-05f2a361a2f9.png" alt="内存区域划分示意图.png"></p><blockquote><p><code>Permanent Generation</code>在<strong>JDK1.8</strong>之后被<code>元空间(Metaspace)</code>替代<br><code>幸存者区（Survivor Space）</code>的<code>S0</code>与<code>S1</code>分别叫做<code>From</code>与<code>To</code>**(没有先后对应关系，向下看)**。</p><ol><li>在GC开始的时候，对象只会存在于<code>Eden区</code>和<code>Survivor区的From</code>，<code>Survivor区的To</code>是空的。</li><li>紧接着进行GC，<code>Eden区</code>中<code>所有存活的对象</code>都会被<code>复制</code>到<code>To</code>，而在<code>From</code>中，仍存活的对象会根据他们的年龄值来决定去向。年龄达到一定值(年龄阈值，可以通过<code>-XX:MaxTenuringThreshold</code>来设置)的对象会被移动到<code>老年代</code>中，没有达到阈值的对象会被复制到<code>To</code>。</li></ol><p>经过这次GC后，<code>Eden区</code>和<code>From</code>已经被清空。这个时候，<code>From</code>和<code>To</code>会交换他们的角色，也就是新的<code>To</code>就是上次GC前的<code>From</code>，新的<code>From</code>就是上次GC前的<code>To</code>。不管怎样，都会保证<code>Survivor区</code>的<code>To</code>的是空的。Minor GC会一直重复这样的过程，直到<code>To</code>被填满，<code>To</code>被填满之后，会将所有对象移动到<code>老年代</code>中。</p></blockquote><h3 id="CMS"><a href="#CMS" class="headerlink" title="CMS"></a>CMS</h3><blockquote><p><code>CMS(Concurrent Mark-Sweep)</code>是以牺牲吞吐量为代价来获得最短回收停顿时间的垃圾回收器。对于要求服务器响应速度的应用上，这种垃圾回收器非常适合。在启动JVM参数加上<code>-XX:+UseConcMarkSweepGC</code>，这个参数表示对于老年代的回收采用CMS。CMS采用的基础算法是：<code>标记—清除</code>。</p></blockquote><h4 id="CMS过程"><a href="#CMS过程" class="headerlink" title="CMS过程"></a>CMS过程</h4><ul><li><strong>初始标记(STW initial mark)</strong> ：在这个阶段，需要虚拟机停顿正在执行的任务，官方的叫法STW(Stop The Word)。这个过程从垃圾回收的”根对象”开始，只扫描到能够和”根对象”直接关联的对象，并作标记。所以这个过程虽然暂停了整个JVM，但是很快就完成了。</li><li><strong>并发标记(Concurrent marking)</strong> ：这个阶段紧随初始标记阶段，在初始标记的基础上继续向下追溯标记。并发标记阶段，应用程序的线程和并发标记的线程并发执行，所以用户不会感受到停顿。</li><li><strong>并发预清理(Concurrent precleaning)</strong> ：并发预清理阶段仍然是并发的。在这个阶段，虚拟机查找在执行并发标记阶段新进入老年代的对象(可能会有一些对象从新生代晋升到老年代， 或者有一些对象被分配到老年代)。通过重新扫描，减少下一个阶段”重新标记”的工作，因为下一个阶段会Stop The World。</li><li><strong>重新标记(STW remark)</strong> ：这个阶段会暂停虚拟机，收集器线程扫描在CMS堆中剩余的对象。扫描从”跟对象”开始向下追溯，并处理对象关联。</li><li><strong>并发清理(Concurrent sweeping)</strong> ：清理垃圾对象，这个阶段收集器线程和应用程序线程并发执行。</li><li><strong>并发重置(Concurrent reset)</strong> ：这个阶段，重置CMS收集器的数据结构，等待下一次垃圾回收。</li></ul><h4 id="CMS缺点"><a href="#CMS缺点" class="headerlink" title="CMS缺点"></a>CMS缺点</h4><ol><li>CMS回收器采用的基础算法是<code>Mark-Sweep</code>。所有CMS<strong>不会</strong>整理、压缩堆空间。这样就会有一个问题：经过CMS收集的堆会产生<code>空间碎片</code>。 CMS不对堆空间整理压缩节约了垃圾回收的停顿时间，但也带来的堆空间的浪费。为了解决堆空间浪费问题，CMS回收器不再采用简单的指针指向一块可用堆空间来为下次对象分配使用。而是把一些未分配的空间汇总成一个列表，当JVM分配对象空间的时候，会搜索这个列表找到足够大的空间来hold住这个对象。</li><li>需要更多的CPU资源。从上面的图可以看到，为了让应用程序不停顿，CMS线程和应用程序线程并发执行，这样就需要有更多的CPU，单纯靠线程切 换是不靠谱的。并且，重新标记阶段，为空保证STW快速完成，也要用到更多的甚至所有的CPU资源。当然，多核多CPU也是未来的趋势！</li><li>CMS的另一个缺点是它需要更大的堆空间。因为CMS标记阶段应用程序的线程还是在执行的，那么就会有堆空间继续分配的情况，为了保证在CMS回 收完堆之前还有空间分配给正在运行的应用程序，必须预留一部分空间。也就是说，CMS不会在老年代满的时候才开始收集。相反，它会尝试更早的开始收集，已避免上面提到的情况：在回收完成之前，堆没有足够空间分配！默认当老年代使用68%的时候，CMS就开始行动了。 <code>–XX:CMSInitiatingOccupancyFraction =n</code> 来设置这个阀值。</li></ol><blockquote><p>总得来说，CMS回收器减少了回收的停顿时间，但是降低了堆空间的利用率。</p></blockquote><h4 id="CMS的适用场景"><a href="#CMS的适用场景" class="headerlink" title="CMS的适用场景"></a>CMS的适用场景</h4><blockquote><p>虽然说了CMS的诸多缺点，但是他还是有自己的适用场景的，并且目前国内很多公司仍然使用者CMS算法，学习他也还是有必要的。</p></blockquote><ul><li>如果你的应用程序对停顿比较敏感，并且在应用程序运行的时候可以提供更大的内存和更多的CPU(也就是硬件配置较高)，那么使用CMS来收集会给你带来好处。</li><li>如果在JVM中，有相对较多存活时间较长的对象(老年代比较大)会更适合使用CMS。</li></ul><h3 id="G1"><a href="#G1" class="headerlink" title="G1"></a>G1</h3><blockquote><p>在G1中，堆被划分成许多个连续的<code>区域(region)</code>。每个区域<strong>大小相等</strong>，在1M~32M之间。JVM最多支持2000个区域，可推算G1能支持的最大内存为2000*32M&#x3D;62.5G。<code>区域(region)</code>的大小在<strong>JVM初始化</strong>的时候决定，也可以用<code>-XX:G1HeapReginSize</code>设置。<br>在G1中没有物理上的<code>Yong(Eden/Survivor)/Old Generation</code>，它们是逻辑的，使用一些<strong>非连续</strong>的区域(Region)组成的。</p></blockquote><p>G1 是一款面向服务端应用的垃圾收集器，它没有新生代和老年代的概念，而是将堆划分为一块块独立的 Region。当要进行垃圾收集时，首先估计每个 Region 中垃圾的数量，每次都从垃圾回收价值最大的 Region 开始回收，因此可以获得最大的回收效率。<br>从整体上看， G1 是基于<strong>标记-整理</strong>算法实现的收集器，从局部（两个 Region 之间）上看是基于<strong>复制</strong>算法实现的，这意味着运行期间不会产生内存空间碎片。</p><blockquote><p>Q:一个对象和它内部所引用的对象可能不在同一个 Region 中，那么当垃圾回收时，是否需要扫描整个堆内存才能完整地进行一次可达性分析？<br>A:并不！每个 Region 都有一个 Remembered Set，用于记录本区域中所有对象引用的对象所在的区域，进行可达性分析时，只要在 GC Roots 中再加上 Remembered Set 即可防止对整个堆内存进行遍历。</p></blockquote><h4 id="G1过程"><a href="#G1过程" class="headerlink" title="G1过程"></a>G1过程</h4><p>如果不计算维护 Remembered Set 的操作，G1 收集器的工作过程分为以下几个步骤：</p><ul><li>初始标记：Stop The World，仅使用一条初始标记线程对所有与 GC Roots 直接关联的对象进行标记。</li><li>并发标记：使用<strong>一条</strong>标记线程与用户线程并发执行。此过程进行可达性分析，速度很慢。</li><li>最终标记：Stop The World，使用多条标记线程并发执行。</li><li>筛选回收：回收废弃对象，此时也要 Stop The World，并使用多条筛选回收线程并发执行。</li></ul><h2 id="CMS回收停顿了几次？为什么停顿？"><a href="#CMS回收停顿了几次？为什么停顿？" class="headerlink" title="CMS回收停顿了几次？为什么停顿？"></a>CMS回收停顿了几次？为什么停顿？</h2><blockquote><p>首先CMS回收停顿了两次。一次是<code>initial mark</code>阶段，一次是<code>remark</code>阶段。具体请参考上一个问题CMS过程。</p></blockquote><h2 id="Java栈什么时候会发生内存溢出，Java堆呢，说一种场景"><a href="#Java栈什么时候会发生内存溢出，Java堆呢，说一种场景" class="headerlink" title="Java栈什么时候会发生内存溢出，Java堆呢，说一种场景"></a>Java栈什么时候会发生内存溢出，Java堆呢，说一种场景</h2><blockquote><p>这里就把常见的都说一说。</p></blockquote><h3 id="Java栈溢出-StackOverflowError"><a href="#Java栈溢出-StackOverflowError" class="headerlink" title="Java栈溢出(StackOverflowError)"></a>Java栈溢出(StackOverflowError)</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">StackOverFlowTest</span>&#123;     </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">stackOverFlowMethod</span><span class="params">()</span>&#123;    </span><br><span class="line">        stackOverFlowMethod();<span class="comment">/* 导致栈溢出的就是这句 */</span></span><br><span class="line">    &#125;    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span>&#123;    </span><br><span class="line">        <span class="type">OOMTest</span> <span class="variable">oom</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">OOMTest</span>();    </span><br><span class="line">        oom.stackOverFlowMethod();    </span><br><span class="line">    &#125;    </span><br><span class="line">&#125; </span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="堆溢出-OutOfMemoryError-java-heap-space"><a href="#堆溢出-OutOfMemoryError-java-heap-space" class="headerlink" title="堆溢出(OutOfMemoryError:java heap space)"></a>堆溢出(OutOfMemoryError:java heap space)</h3><p>堆内存溢出的时候，虚拟机会抛出<code>java.lang.OutOfMemoryError:java heap space</code>,出现此种情况的时候，我们需要根据内存溢出的时候产生的dump文件来具体分析（需要增加<code>-XX:+HeapDumpOnOutOfMemoryErrorjvm</code>启动参数）。出现此种问题的时候有可能是内存泄露，也有可能是内存溢出了。</p><ul><li>如果内存泄露，我们要找出泄露的对象是怎么被GC ROOT引用起来，然后通过引用链来具体分析泄露的原因。</li><li>如果出现了内存溢出问题，这往往是程序本生需要的内存大于了我们给虚拟机配置的内存，这种情况下，我们可以采用调大<code>-Xmx</code>来解决这种问题。</li></ul><p>内存溢出代码示例</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><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_">OOMTest</span>&#123;    </span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span>&#123;    </span><br><span class="line">                List&lt;<span class="type">byte</span>[]&gt; buffer = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;<span class="type">byte</span>[]&gt;();    </span><br><span class="line">                buffer.add(<span class="keyword">new</span> <span class="title class_">byte</span>[<span class="number">10</span>*<span class="number">1024</span>*<span class="number">1024</span>]);    </span><br><span class="line">        &#125;    </span><br><span class="line"></span><br><span class="line">&#125; </span><br><span class="line"></span><br></pre></td></tr></table></figure><p>通过如下命令启动</p><figure class="highlight 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"></span><br><span class="line">java -verbose:gc -Xmn10M -Xms20M -Xmx20M -XX:+PrintGC OOMTest</span><br><span class="line"></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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">[GC 1180K-&gt;366K(19456K), 0.0037311 secs]    </span><br><span class="line">[Full GC 366K-&gt;330K(19456K), 0.0098740 secs]    </span><br><span class="line">[Full GC 330K-&gt;292K(19456K), 0.0090244 secs]    </span><br><span class="line">Exception in thread &quot;main&quot; java.lang.OutOfMemoryError: Java heap space    </span><br><span class="line">        at OOMTest.main(OOMTest.java:7)    </span><br><span class="line"></span><br></pre></td></tr></table></figure><p>从运行结果可以看出，JVM进行了一次<code>Minor gc</code>和两次的<code>Major gc</code>，从<code>Major gc</code>的输出可以看出，gc以后<code>old区</code>使用率为134K，而字节数组为10M，加起来大于了<code>old generation</code>的空间，所以抛出了异常，如果调整<code>-Xms21M,-Xmx21M</code>,那么就不会触发gc操作也不会出现异常了。</p><blockquote><p>从侧面验证了一个结论：当对象大于新生代剩余内存的时候，将直接放入老年代，当老年代剩余内存还是无法放下的时候，触发垃圾收集，收集后还是不能放下就会抛出内存溢出异常了。</p></blockquote><h3 id="持久带溢出-OutOfMemoryError-PermGen-space"><a href="#持久带溢出-OutOfMemoryError-PermGen-space" class="headerlink" title="持久带溢出(OutOfMemoryError: PermGen space)"></a>持久带溢出(OutOfMemoryError: PermGen space)</h3><p>我们知道Hotspot jvm通过持久带实现了Java虚拟机规范中的方法区，而运行时的常量池就是保存在方法区中的，因此持久带溢出有可能是运行时常量池溢出，也有可能是方法区中保存的class对象没有被及时回收掉或者class信息占用的内存超过了我们配置。<br>当持久带溢出的时候抛出<code>java.lang.OutOfMemoryError: PermGen space</code>。</p><ul><li>使用一些应用服务器的热部署的时候，我们就会遇到热部署几次以后发现内存溢出了，这种情况就是因为每次热部署的后，原来的Class没有被卸载掉。</li><li>如果应用程序本身比较大，涉及的类库比较多，但是我们分配给持久带的内存（通过<code>-XX:PermSize</code>和<code>-XX:MaxPermSize</code>来设置）比较小的时候也可能出现此种问题。</li><li>一些第三方框架，比如<code>spring</code>,<code>hibernate</code>都通过字节码生成技术（比如<code>CGLib</code>）来实现一些增强的功能，这种情况可能需要更大的方法区来存储动态生成的Class文件。</li></ul><p>我们知道Java中字符串常量是放在常量池中的，<code>String.intern()</code>这个方法运行的时候，会检查常量池中是否存和本字符串相等的对象，如果存在直接返回对常量池中对象的引用，不存在的话，先把此字符串加入常量池，然后再返回字符串的引用。那么我们就可以通过<code>String.intern()</code>方法来模拟一下运行时常量区的溢出。</p><p>代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OOMTest</span>&#123;    </span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String... args)</span>&#123;    </span><br><span class="line">                List&lt;String&gt; list = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;String&gt;();    </span><br><span class="line">                <span class="keyword">while</span>(<span class="literal">true</span>)&#123;    </span><br><span class="line">                        list.add(UUID.randomUUID().toString().intern());    </span><br><span class="line">                &#125;    </span><br><span class="line">        &#125;        </span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>通过如下命令启动</p><figure class="highlight 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"></span><br><span class="line">java -verbose:gc -Xmn5M -Xms10M -Xmx10M -XX:MaxPermSize=1M -XX:+PrintGC OOMTest</span><br><span class="line"></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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">Exception in thread &quot;main&quot; java.lang.OutOfMemoryError: PermGen space    </span><br><span class="line">        at java.lang.String.intern(Native Method)    </span><br><span class="line">        at OOMTest.main(OOMTest.java:8)   </span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="OutOfMemoryError-unable-to-create-native-thread"><a href="#OutOfMemoryError-unable-to-create-native-thread" class="headerlink" title="OutOfMemoryError:unable to create native thread"></a>OutOfMemoryError:unable to create native thread</h3><p>最后我们在来看看<code>java.lang.OutOfMemoryError:unable to create natvie thread</code>这种错误。 出现这种情况的时候，一般是下面两种情况导致的：</p><ol><li>程序创建的线程数超过了操作系统的限制。对于Linux系统，我们可以通过<code>ulimit -u</code>来查看此限制。</li><li>给虚拟机分配的内存过大，导致创建线程的时候需要的native内存太少。</li></ol><p>操作系统对每个进程的内存是有限制的，我们启动Jvm,相当于启动了一个进程，假如我们一个进程占用了<strong>4G</strong>的内存，那么通过下面的公式计算出来的剩余内存就是建立线程栈的时候可以用的内存。<br><code>线程栈总可用内存</code> &#x3D; <code>4G</code> - <code>(-Xmx的值)</code> - <code>(-XX:MaxPermSize的值)</code> - <code>程序计数器占用的内存</code><br>通过上面的公式我们可以看出，<code>-Xmx</code> 和 <code>MaxPermSize</code> 的值越大，那么留给线程栈可用的空间就越小，在<code>-Xss</code>参数配置的栈容量不变的情况下，可以创建的线程数也就越小。因此如果是因为这种情况导致的<code>unable to create native thread</code>,那么要么我们增大进程所占用的总内存，或者减少<code>-Xmx</code>或者<code>-Xss</code>来达到创建更多线程的目的。</p><h3 id="面试者的回答"><a href="#面试者的回答" class="headerlink" title="面试者的回答"></a>面试者的回答</h3><blockquote><p>这一题面试者在面试的时候回答了集合类持有对象:如果对象的引用刚好被单例所持有的话，JVM就不会回收该引用。解决方案最简单只需要将单例中持有对象的变量使用<code>static</code>修饰，并在初始化时置为<code>null</code>。</p></blockquote><h2 id="软引用和弱引用的区别"><a href="#软引用和弱引用的区别" class="headerlink" title="软引用和弱引用的区别"></a><code>软引用</code>和<code>弱引用</code>的区别</h2><blockquote><p>这个问题引申自上一个问题<br>Q:集合类如何解决OOM问题<br>A:用<code>软引用</code>和<code>弱引用</code>。<br>四种引用的定义参考问题<code>Java对象的回收方式，回收算法</code>下的<code>引用种类</code></p></blockquote><ul><li><p>在应用程序中，我们使用引用类可以边面在程序执行期间将对象留在内存中。如果我们一软引用，弱引用或虚引用的方式引用对象，这样垃圾收集器就能够随意的释放对象。如果希望尽可能的减少程序在其生命周期中所占的内存大小时，这些引用类就很有好处。</p></li><li><p>必须指出的一个问题:要使用这些特殊的引用类，就不能保留对对象的强引用。如果保留了对对象的强引用，那么就会浪费这些类所提供的任何好处。</p></li><li><p>四种引用最主要的区别就是垃圾回收器回收的时机不同：</p><ol><li><strong>强引用</strong>: 我们经常使用的一种引用。基本上垃圾回收器不会主动的去回收</li><li><strong>弱引用</strong>: 垃圾回收器会立刻回收弱引用。</li><li><strong>软引用</strong>: 在JVM没有出现内存不足的情况下，垃圾回收器不会去主动回收软引用</li><li><strong>虚引用</strong>: 虚引用引用的字符串会被垃圾回收器回收， 自己本身会被添加到关联的引用队列中去。</li></ol></li></ul><h2 id="Java里的锁了解哪些"><a href="#Java里的锁了解哪些" class="headerlink" title="Java里的锁了解哪些"></a>Java里的锁了解哪些</h2><blockquote><p>面试者说了Lock和synchronized</p></blockquote><h3 id="并发关键字"><a href="#并发关键字" class="headerlink" title="并发关键字"></a>并发关键字</h3><h4 id="volatile短见字"><a href="#volatile短见字" class="headerlink" title="volatile短见字"></a>volatile短见字</h4><p>可以这样说，volatile 关键字是 Java 虚拟机提供的<strong>轻量级的同步机制</strong>。 </p><h5 id="功能"><a href="#功能" class="headerlink" title="功能"></a>功能</h5><p>volatile 有 2 个主要功能：</p><ul><li><strong>可见性</strong>。一个线程对共享变量的修改，其他线程能够立即得知这个修改。普通变量不能做到这一点，普通变量的值在线程间传递需要通过主内存来完成。</li><li><strong>禁止指令重排序</strong>。</li></ul><h5 id="底层原理"><a href="#底层原理" class="headerlink" title="底层原理"></a>底层原理</h5><p>加入 volatile 关键字时，会多出 <strong>lock 前缀指令</strong>， 该 lock 前缀指令相当于内存屏障，内存屏障会提供 3 个功能：</p><ul><li>在执行到内存屏障这句指令时，在其前面的操作都已完成了</li><li>强制将<strong>处理器行的数据</strong>缓存写回内存</li><li>一个处理器的缓存回写到内存会导致其他工作内存中的缓存失效</li></ul><h5 id="应用场景"><a href="#应用场景" class="headerlink" title="应用场景"></a>应用场景</h5><ul><li><p>状态标记</p><p>volatile + boolean</p></li><li><p>DCL 单例模式（Double Check Lock，双重校验锁）</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><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="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Singleton</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">volatile</span> <span class="keyword">static</span> Singleton singleton=<span class="literal">null</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="title function_">Singleton</span><span class="params">()</span>&#123;&#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> Singleton <span class="title function_">getSingleton</span><span class="params">()</span>&#123;</span><br><span class="line">        <span class="keyword">if</span>(singleton==<span class="literal">null</span>)&#123;</span><br><span class="line">            <span class="keyword">synchronized</span> (Singleton.class)&#123;</span><br><span class="line">                <span class="keyword">if</span>(singleton==<span class="literal">null</span>)&#123;</span><br><span class="line">                    singleton=<span class="keyword">new</span> <span class="title class_">Singleton</span>();</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> singleton;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure></li></ul><h4 id="synchronized关键字"><a href="#synchronized关键字" class="headerlink" title="synchronized关键字"></a>synchronized关键字</h4><p><strong>线程安全问题</strong>:</p><ul><li>存在共享数据（临界资源）</li><li>存在多条线程共同操作这些共享数据</li></ul><p>解决：<strong>同步机制</strong>。</p><p>同一时刻有且只有一个线程在操作共享数据，其他线程必须等到该线程处理完数据后再对共享数据进行操作。</p><p>同步的前提：</p><ul><li>多个线程</li><li>多个线程使用的是同一个锁对象</li></ul><p>同步的弊端：</p><p>当线程相当多时，因为每个线程都会去判断同步上的锁，这是很耗费资源的，无形中会降低程序的运行效率。</p><h5 id="功能-1"><a href="#功能-1" class="headerlink" title="功能"></a>功能</h5><p>使用 synchroinzed 进行同步，可以保证原子性（保证每个时刻只有一个线程执行同步代码）和可见性（对一个变量执行 unlock 操作之前，必须把变量值同步回主内存）。</p><h5 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h5><p>synchronized 修饰的对象有几种：</p><ul><li><p>修饰一个类。</p><p>作用范围是 synchronized 后面括号括起来的部分</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">ClassName</span> &#123;</span><br><span class="line">   <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">method</span><span class="params">()</span> &#123;</span><br><span class="line">      <span class="keyword">synchronized</span>(ClassName.class) &#123;</span><br><span class="line">         <span class="comment">// todo</span></span><br><span class="line">      &#125;</span><br><span class="line">   &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure></li><li><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title function_">method</span><span class="params">()</span>&#123;</span><br><span class="line">   <span class="comment">// todo</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure></li><li><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">synchronized</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">method</span><span class="params">()</span> &#123;</span><br><span class="line">   <span class="comment">// todo</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure></li><li><p>修饰一个代码块：被修饰的代码块称为同步语句块</p><p>作用范围是大括号 {} 括起来的代码块</p><p>作用对象是调用这个代码块的对象</p></li></ul><blockquote><p><strong>注意</strong>：如果锁的是类对象的话，尽管new多个实例对象，但他们仍然是属于同一个类依然会被锁住，即线程之间保证同步关系。</p></blockquote><h5 id="原理"><a href="#原理" class="headerlink" title="原理"></a>原理</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SynchronizedDemo</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="keyword">synchronized</span> (SynchronizedDemo.class) &#123; <span class="comment">//锁住类对象</span></span><br><span class="line">        &#125;</span><br><span class="line">        method();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">synchronized</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">method</span><span class="params">()</span> &#123; <span class="comment">//锁住类对象</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/09/08/251b85c0-d18f-11e9-a9ea-05f2a361a2f9.png" alt="字节码分析.png"></p><p>任意一个对象都拥有自己的<code>Monitor</code>，当这个对象由同步块或者同步方法调用时， 执行方法的线程必须先获取该对象的<code>Monitor</code>才能进入同步块和同步方法， 如果没有获取到<code>Monitor</code>的线程将会被阻塞在同步块和同步方法的入口处，进入到<code>BLOCKED</code>状态。</p><p><code>synchronized</code>同步语句块的实现使用的是<code>monitorenter</code>和<code>monitorexit</code>指令。</p><p><strong>monitorenter 指令指向同步代码块的开始位置，monitorexit 指令则指明同步代码块的结束位置。</strong></p><p>使用<code>synchronized</code>进行同步，其关键就是必须要对对象的<code>Monitor</code>进行获取， 当<strong>线程获取<code>Monitor</code>后才能继续往下执行，否则就只能等待</strong>。 而这个获取的过程是<strong>互斥</strong>的，即<strong>同一时刻只有一个线程能够获取到 Monitor</strong>。</p><p>上面的<code>SynchronizedDemo</code>中在执行完同步代码块之后紧接着再会去执行一个静态同步方法，而这个方法锁的对象依然就这个类对象， 那么这个正在执行的线程还需要获取该锁吗？</p><p>答案是不必的，从上图中就可以看出来， 执行静态同步方法的时候就只有一条<code>monitorexit</code>指令，并没有<code>monitorenter</code>获取锁的指令。 这就是<strong>锁的重入性</strong>， 即在同一线程中，线程不需要再次获取同一把锁。 <code>synchronized</code>先天具有重入性。 每个对象拥有一个<strong>计数器</strong>，当线程获取该对象锁后，计数器就会<code>+1</code>，释放锁后就会将计数器<code>-1</code>。</p><p><code>synchronized</code>修饰的方法并没有<code>monitorenter</code>指令和<code>monitorexit</code>指令，取得代之的是 <code>ACC_SYNCHRONIZED</code> 标识，该标识指明了该方法是一个同步方法，JVM 通过该 <code>ACC_SYNCHRONIZED</code> 访问标志来辨别一个方法是否声明为同步方法，从而执行相应的同步调用。</p><h5 id="锁优化策略"><a href="#锁优化策略" class="headerlink" title="锁优化策略"></a>锁优化策略</h5><p>JDK 1.6 之后对<code>synchronized</code>进行优化。</p><p>锁的 4 种状态：</p><ul><li>无锁</li><li>偏向锁</li><li>轻量级锁</li><li>重量级锁</li></ul><h6 id="自旋锁"><a href="#自旋锁" class="headerlink" title="自旋锁"></a>自旋锁</h6><p>在很多应用上，<strong>共享数据的锁定状态只会持续很短一段时间</strong>。自旋锁的思想是让一个线程在请求一个共享数据的锁时执行忙循环（自旋）一段时间，如果在这段时间内能获得锁，就可以避免进入阻塞状态。</p><p>自旋锁虽然能避免进入阻塞状态从而减少开销，但是它需要进行忙循环操作占用 CPU 时间，它只适用于共享数据的锁定状态很短的场景。</p><p>在 JDK 1.6 中引入了<strong>自适应的自旋锁</strong>。自适应意味着自旋的次数不再固定了，而是由前一次在同一个锁上的自旋次数及锁的拥有者的状态来决定。</p><h6 id="锁消除"><a href="#锁消除" class="headerlink" title="锁消除"></a>锁消除</h6><p>锁消除是指对于<strong>被检测出不可能存在竞争的共享数据的锁进行消除</strong>。</p><p>锁消除主要是通过<strong>逃逸分析来支持</strong>，如果堆上的共享数据不可能逃逸出去被其它线程访问到，那么就可以把它们当成私有数据对待，也就可以将它们的锁进行消除。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> String <span class="title function_">concatString</span><span class="params">(String s1, String s2, String s3)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> s1 + s2 + s3;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><code>String</code>是一个不可变的类，编译器会对<code>String</code>的拼接自动优化。在<code>JDK 1.5</code>之前，会转化为 <code>StringBuffer</code>对象的连续<code>append()</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="keyword">public</span> <span class="keyword">static</span> String <span class="title function_">concatString</span><span class="params">(String s1, String s2, String s3)</span> &#123;</span><br><span class="line">    <span class="type">StringBuffer</span> <span class="variable">sb</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuffer</span>();</span><br><span class="line">    sb.append(s1);</span><br><span class="line">    sb.append(s2);</span><br><span class="line">    sb.append(s3);</span><br><span class="line">    <span class="keyword">return</span> sb.toString();</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>每个<code>append()</code>方法中都有一个同步块。虚拟机观察变量<code>sb</code>，很快就会发现它的动态作用域被限制在<code>concatString()</code>方法内部。也就是说，<code>sb</code>的所有引用永远不会逃逸到<code>concatString()</code>方法之外，其他线程无法访问到它，因此可以进行消除。</p><h6 id="锁粗化"><a href="#锁粗化" class="headerlink" title="锁粗化"></a>锁粗化</h6><p><strong>如果一系列的连续操作都对同一个对象反复加锁和解锁，频繁的加锁操作就会导致性能损耗</strong>。</p><p>上一节的示例代码中连续的<code>append()</code>方法就属于这类情况。如果虚拟机探测到由这样的一串零碎的操作都对同一个对象加锁，将会把加锁的范围扩展（粗化）到整个操作序列的外部。对于上一节的示例代码就是扩展到第一个 <code>append()</code>操作之前直至最后一个<code>append()</code>操作之后，这样只需要加锁一次就可以了。</p><h6 id="偏向锁"><a href="#偏向锁" class="headerlink" title="偏向锁"></a>偏向锁</h6><p>在大多数情况下，<strong>锁不仅不存在多线程竞争，而且总是由同一个线程多次获得</strong>。偏向锁的思想是偏向于第一个获取锁对象的线程，这个线程在之后获取该锁就不再需要进行同步操作，甚至连<code>CAS</code>操作也不再需要。</p><h6 id="轻量级锁"><a href="#轻量级锁" class="headerlink" title="轻量级锁"></a>轻量级锁</h6><p>轻量级锁是由偏向锁升级而来，偏向锁运行在一个线程进入同步块的情况下，当第二个线程加入锁争用的时候，偏向锁就会升级为轻量级锁。</p><p><strong>对于绝大部分的锁，在整个同步周期内都是不存在竞争的</strong>，因此也就不需要都使用互斥量进行同步，可以<strong>先采用<code>CAS</code>操作进行同步</strong>，如果<code>CAS</code>失败了再改用互斥量进行同步。</p><h4 id="volatile-与-synchronized-比较"><a href="#volatile-与-synchronized-比较" class="headerlink" title="volatile 与 synchronized 比较"></a>volatile 与 synchronized 比较</h4><ul><li>volatile 是 JVM 轻量级的同步机制，所以性能比 synchronized 要好</li><li>volatile 只能修饰变量<br>synchronized 可以修饰代码块或者方法</li><li>多线程访问 volatile 不会出现阻塞<br>synchronized 会出现阻塞</li><li>volatile 只能保证可见性，不能保证原子性<br>synchroinzed 能保证原子性，也间接保证了可见性</li><li>volatile 修饰的变量不会被编译器优化<br>synchronized 修饰的变量可以被编译器优化</li></ul><h3 id="Lock-体系"><a href="#Lock-体系" class="headerlink" title="Lock 体系"></a>Lock 体系</h3><h4 id="Condition-条件对象"><a href="#Condition-条件对象" class="headerlink" title="Condition 条件对象"></a>Condition 条件对象</h4><p>条件对象是<strong>线程同步对象中的一种</strong>，主要用来等待某种条件的发生，条件发生后，可以唤醒等待在该条件上的一个线程或者所有线程。</p><p><strong>条件对象要与锁一起协同工作</strong>。通过<code>ReentrantLock</code>的<code>newCondtion</code>获取实例对象。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">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="type">Condition</span> <span class="variable">condition</span> <span class="operator">=</span> lock.newCondition();</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>注意：</p><ul><li><p>Condition 中有<code>await</code>、<code>signal</code>、<code>signalAll</code> ，分别对应 <code>Object</code> 中放入 <code>wait</code>、<code>notify</code>、<code>notifyAll</code> 方法，其实 Condition 也有上述三种方法，改变方法名称是为了避免使用上语义的混淆。</p><p>await 和 signal &#x2F; signalAll 方法就像一个开关控制着线程 A（等待方）和线程 B（通知方）。</p><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/09/08/1034c3a0-d190-11e9-a9ea-05f2a361a2f9.png" alt="Lock-Condition演示.png"></p><p>线程 <code>awaitThread</code> 先通过 <code>lock.lock()</code> 方法获取锁成功后调用了 <code>condition.await()</code> 方法进入<strong>等待队列</strong>， </p><p>另一个线程 signalThread 通过 <code>lock.lock()</code> 方法获取锁成功后调用了 <strong>condition.signal &#x2F; signalAll， 使得线程 awaitThread 能够有机会移入到同步队列</strong>中， 当其他线程释放 Lock 后使得线程 awaitThread 能够有机会获取 Lock， 从而使得线程 awaitThread 能够从 await 方法中退出，然后执行后续操作。 如果 awaitThread 获取 Lock 失败会直接进入到同步队列。</p></li><li><p>一个 Lock 可以与多个 Condition 对象绑定。</p></li></ul><h4 id="AQS"><a href="#AQS" class="headerlink" title="AQS"></a>AQS</h4><p>AQS(AbtsractQueueSynchronized) 即同步队列器。</p><p>AQS 是一个抽象类，本身并没有实现任何同步接口的，只是通过提供<strong>同步状态的获取和释放</strong>来供自定义的同步组件使用。</p><p>AQS 的实现依赖内部的双向队列（底层是双向链表）。</p><p>如果当前线程获取同步状态失败，则会将该线程以及等待状态等信息封装为 Node，将其<strong>加入同步队列的尾部，同时阻塞当前线程</strong>，当同步状态释放时，唤醒队列的头结点。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">transient</span> <span class="keyword">volatile</span> Node head; <span class="comment">//同步队列的头结点</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">transient</span> <span class="keyword">volatile</span> Node tail; <span class="comment">//同步队列的尾结点</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">volatile</span> <span class="type">int</span> state; <span class="comment">//同步状态。</span></span><br><span class="line"><span class="comment">// state=0,表示同步状态可用；state=1，表示同步状态已被占用</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><h4 id="可重入"><a href="#可重入" class="headerlink" title="可重入"></a>可重入</h4><p>某个线程试图获取一个已经有该线程持有的锁，那么这个请求就会成功。“重入”意味着获取的锁的操作的粒度是“线程”而不是“调用”。重入的一种实现方法是，为每个锁关联一个<strong>计数器</strong>（方便解锁）和一个<strong>所有者线程</strong>（知道是哪个线程是可重入的）。</p><h4 id="公平锁与非公平锁"><a href="#公平锁与非公平锁" class="headerlink" title="公平锁与非公平锁"></a>公平锁与非公平锁</h4><p>公平锁是指多个线程在等待同一个锁时，<strong>按照申请锁的顺序来依次获取锁</strong>。</p><table><thead><tr><th align="center">公平锁</th><th align="center">非公平锁</th></tr></thead><tbody><tr><td align="center">公平锁每次获取到锁为同步队列中的第一个节点，<br>保证请求资源时间上的绝对顺序</td><td align="center">非公平锁有可能刚释放锁的线程下次继续获取该锁，<br>则有可能导致其他线程永远无法获取到锁，造成“饥饿”现象。</td></tr><tr><td align="center">公平锁为了保证时间上的绝对顺序，<br>需要频繁的上下文切换</td><td align="center">非公平锁会<strong>降低一定的上下文切换</strong>，降低性能开销<br>因此，ReentrantLock 默认选择的是非公平锁</td></tr></tbody></table><h4 id="独占锁和共享锁"><a href="#独占锁和共享锁" class="headerlink" title="独占锁和共享锁"></a>独占锁和共享锁</h4><p>独占锁模式下，每次只能有一个线程能持有锁，ReentrantLock 就是以独占方式实现的互斥锁。</p><p>共享锁，则允许多个线程同时获取锁，并发访问共享资源，如：ReadWriteLock。</p><p>很显然，独占锁是一种悲观保守的加锁策略，它避免了读&#x2F;读冲突，如果某个只读线程获取锁，则其他读线程都只能等待，这种情况下就限制了不必要的并发性，因为读操作并不会影响数据的一致性。</p><p>共享锁则是一种乐观锁，它放宽了加锁策略，允许多个执行读操作的线程同时访问共享资源。</p><h4 id="ReentrantLock"><a href="#ReentrantLock" class="headerlink" title="ReentrantLock"></a>ReentrantLock</h4><p>ReentrantLock 即可重入锁，有 3 个内部类：Sync、FairSync 和 NonfairSync。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">abstract</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Sync</span> <span class="keyword">extends</span> <span class="title class_">AbstractQueuedSynchronizer</span> &#123;</span><br><span class="line">    <span class="comment">//...</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">FairSync</span> <span class="keyword">extends</span> <span class="title class_">Sync</span> &#123;</span><br><span class="line">    <span class="comment">//...</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">NonfairSync</span> <span class="keyword">extends</span> <span class="title class_">Sync</span> &#123;</span><br><span class="line">    <span class="comment">//...</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><ul><li><p>Sync 是一个继承 AQS 的抽象类，并发控制就是通过 Sync 实现的。</p><p>重写了 tryRelease() , 有两个子类 FiarSync 和 NonfairSync，即公平锁和非公平锁。</p></li><li><p>由于 Sync 重写 tryRealese()  方法，并且 FairSync 和 NonfairSync没有再次重写该方法，所以 <strong>公平锁和非公平锁释放锁的操作是一样的</strong>，即<strong>唤醒等待队列中第一个被挂起的线程</strong>。</p></li><li><p>公平锁和非公平锁获取锁的方式是不同的。</p><p>公平锁获取锁时，如果一个线程已经获取了锁，其他线程都会被挂起进入等待队列，后面来的<strong>线程等待的时间</strong>没有等待队列中线程等待的时间长的话，那么就会放弃获取锁，直接进入等待队列；</p><p>非公平锁获取锁的方式是一种<strong>抢占式</strong>的，不考虑线程等待时间，无论是哪个线程获取了锁，则其他线程就进入等待队列。</p></li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> Sync sync;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="title function_">ReentrantLock</span><span class="params">()</span> &#123; <span class="comment">//默认是非公平锁</span></span><br><span class="line">    sync = <span class="keyword">new</span> <span class="title class_">NonfairSync</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="title function_">ReentrantLock</span><span class="params">(<span class="type">boolean</span> fair)</span> &#123; <span class="comment">//可设置为公平锁</span></span><br><span class="line">    sync = fair ? <span class="keyword">new</span> <span class="title class_">FairSync</span>() : <span class="keyword">new</span> <span class="title class_">NonfairSync</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h4 id="ReentrantLock-与-synchronized-的区别"><a href="#ReentrantLock-与-synchronized-的区别" class="headerlink" title="ReentrantLock 与 synchronized 的区别"></a>ReentrantLock 与 synchronized 的区别</h4><ul><li><p><strong>锁的实现</strong>：</p><p>synchronized 是 JVM 实现的，ReentrantLock 是 JDK 实现的。 </p></li><li><p><strong>性能</strong>：</p><p>JDK1.6 后对 synchronized 进行了很多优化，两者的性能大致相同。</p></li><li><p><strong>等待可中断</strong>：</p><p>当持有锁的线程长期未释放锁时，正在等待的线程可选择放弃等待，改为处理其他事情。</p><p>ReentrantLock 是等待可中断的，synchronized  则不行。</p></li><li><p><strong>公平锁</strong>：</p><p>公平锁是指多个线程在等待同一个锁时，<strong>按照申请锁的顺序来依次获取锁</strong>。</p><p>synchronized 默认是非公平锁，ReentrantLock 既可以是公平锁，又可以是非公平锁。</p></li><li><p><strong>锁绑定多个条件</strong>：</p><p>一个 ReentrantLock 可以绑定多个 Condition 对象。</p></li></ul><h4 id="LockSupport"><a href="#LockSupport" class="headerlink" title="LockSupport"></a>LockSupport</h4><p>LockSupport 位于 java.util.concurrent.locks 包下。 LockSupprot 是线程的<strong>阻塞原语</strong>，用来<strong>阻塞线程</strong>和<strong>唤醒线程</strong>。 </p><p>每个使用 LockSupport 的线程都会与一个许可关联， </p><p>如果该许可可用，并且可在线程中使用，则调用 park() 将会立即返回，否则可能阻塞。 </p><p>如果许可尚不可用，则可以调用 unpark 使其可用。 </p><p>但是注意<strong>许可不可重入</strong>，也就是说只能调用一次 park() 方法，否则会一直阻塞。</p><h5 id="LockSupport-中方法"><a href="#LockSupport-中方法" class="headerlink" title="LockSupport 中方法"></a>LockSupport 中方法</h5><table><thead><tr><th align="center">方法</th><th align="center">说明</th></tr></thead><tbody><tr><td align="center">void park()</td><td align="center">阻塞当前线程，如果调用 unpark() 方法或者当前线程被中断， 能从 park()方法中返回</td></tr><tr><td align="center">void park(Object blocker)</td><td align="center">功能同park()，入参增加一个Object对象，用来记录导致线程阻塞的阻塞对象，方便进行问题排查</td></tr><tr><td align="center">void parkNanos(long nanos)</td><td align="center">阻塞当前线程，最长不超过nanos纳秒，增加了超时返回的特性</td></tr><tr><td align="center">void parkNanos(Object blocker, long nanos)</td><td align="center">功能同 parkNanos(long nanos)，入参增加一个 Object 对象，用来记录导致线程阻塞的阻塞对象，方便进行问题排查</td></tr><tr><td align="center">void parkUntil(long deadline)</td><td align="center">阻塞当前线程，deadline 已知</td></tr><tr><td align="center">void parkUntil(Object blocker, long deadline)</td><td align="center">功能同 parkUntil(long deadline)，入参增加一个 Object 对象，用来记录导致线程阻塞的阻塞对象，方便进行问题排查</td></tr><tr><td align="center">void unpark(Thread thread)</td><td align="center">唤醒处于阻塞状态的指定线程</td></tr></tbody></table><p>实际上 LockSupport 阻塞和唤醒线程的功能是<strong>依赖于 sun.misc.Unsafe</strong>，比如 park() 方法的功能实现则是靠unsafe.park() 方法。 另外在阻塞线程这一系列方法中还有一个很有意思的现象：每个方法都会新增一个带有Object 的阻塞对象的重载方法。 那么增加了一个 Object 对象的入参会有什么不同的地方了？</p><ul><li>调用 park() 方法 dump 线程：</li></ul><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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">&quot;main&quot; #1 prio=5 os_prio=0 tid=0x02cdcc00 nid=0x2b48 waiting on condition [0x00d6f000]</span><br><span class="line">   java.lang.Thread.State: WAITING (parking)</span><br><span class="line">        at sun.misc.Unsafe.park(Native Method)</span><br><span class="line">        at java.util.concurrent.locks.LockSupport.park(LockSupport.java:304)</span><br><span class="line">        at learn.LockSupportDemo.main(LockSupportDemo.java:7)</span><br><span class="line"></span><br></pre></td></tr></table></figure><ul><li>调用 park(Object blocker) 方法 dump 线程:</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">&quot;main&quot;</span> #<span class="number">1</span> prio=<span class="number">5</span> os_prio=<span class="number">0</span> tid=<span class="number">0x0069cc00</span> nid=<span class="number">0x6c0</span> waiting on condition [<span class="number">0x00dcf000</span>]</span><br><span class="line">   java.lang.Thread.State: WAITING (parking)</span><br><span class="line">        at sun.misc.Unsafe.park(Native Method)</span><br><span class="line">        - parking to wait <span class="keyword">for</span>  &lt;<span class="number">0x048c2d18</span>&gt; (a java.lang.String)</span><br><span class="line">        at java.util.concurrent.locks.LockSupport.park(LockSupport.java:<span class="number">175</span>)</span><br><span class="line">        at learn.LockSupportDemo.main(LockSupportDemo.java:<span class="number">7</span>)</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>通过分别调用这两个方法然后 dump 线程信息可以看出， 带 Object 的 park 方法相较于无参的 park 方法会增加</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">- parking to wait <span class="keyword">for</span>  &lt;<span class="number">0x048c2d18</span>&gt; (a java.lang.String)</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>这种信息就类似于记录<strong>案发现场</strong>，有助于工程人员能够迅速发现问题解决问题。</p><p>注意：</p><ul><li>synchronized 使线程阻塞，线程会进入到 BLOCKED 状态</li><li>调用 LockSupprt 方法阻塞线程会使线程进入到 WAITING 状态</li></ul><h5 id="LockSupport-使用示例"><a href="#LockSupport-使用示例" class="headerlink" title="LockSupport 使用示例"></a>LockSupport 使用示例</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.util.concurrent.locks.LockSupport;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">LockSupportExample</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">Thread</span> <span class="variable">t1</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(() -&gt; &#123;</span><br><span class="line">            LockSupport.park();</span><br><span class="line">            System.out.println(Thread.currentThread().getName() + <span class="string">&quot;被唤醒&quot;</span>);</span><br><span class="line">        &#125;);</span><br><span class="line">        <span class="type">Thread</span> <span class="variable">t2</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(() -&gt; &#123;</span><br><span class="line">            LockSupport.park();</span><br><span class="line">            System.out.println(Thread.currentThread().getName() + <span class="string">&quot;被唤醒&quot;</span>);</span><br><span class="line">        &#125;);</span><br><span class="line">        t1.start();</span><br><span class="line">        t2.start();</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            Thread.sleep(<span class="number">3000</span>);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">        LockSupport.unpark(t1);</span><br><span class="line">        LockSupport.unpark(t2);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><figure class="highlight html"><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">Thread-0被唤醒</span><br><span class="line">Thread-1被唤醒</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>t1 线程调用<code>LockSupport.park()</code>使<code>t1</code>阻塞， 当<code>mian</code>线程睡眠<code>3</code>秒结束后通过 <code>LockSupport.unpark(t1)</code>方法唤醒<code>t1 线程</code>,<code>t1 线程</code>被唤醒执行后续操作。 另外，还有一点值得关注的是，<code>LockSupport.unpark(t1)</code>可以<strong>通过指定线程对象唤醒指定的线程</strong>。</p><h2 id="它们-Lock和synchronized-的使用方式和实现原理有什么区别呢？"><a href="#它们-Lock和synchronized-的使用方式和实现原理有什么区别呢？" class="headerlink" title="它们(Lock和synchronized)的使用方式和实现原理有什么区别呢？"></a>它们(Lock和synchronized)的使用方式和实现原理有什么区别呢？</h2><blockquote><p>详见上一问的回答，已经解释的很详细了；</p></blockquote><h2 id="synchronized锁升级的过程"><a href="#synchronized锁升级的过程" class="headerlink" title="synchronized锁升级的过程"></a>synchronized锁升级的过程</h2><blockquote><p>面试者的回答：偏向锁到轻量级锁再到重量级锁<br>Q:它们分别是怎么实现的，解决的是哪些问题，什么时候会发生锁升级？</p></blockquote><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/09/08/8293b4c0-d1fd-11e9-93b8-c3a852f09de8.png" alt="锁状态示意图.png"></p><blockquote><p>在 jdk6 之后便引入了<strong>偏向锁</strong>和<strong>轻量级锁</strong>，所以总共有4种锁状态，级别由低到高依次为：<strong>无锁状态</strong>、<strong>偏向锁状态</strong>、<strong>轻量级锁状态</strong>、<strong>重量级锁状态</strong>。这几个状态会随着竞争情况逐渐升级。</p></blockquote><blockquote><p><strong>注意</strong>：锁可以升级但不能降级。</p></blockquote><blockquote><p>在使用<code>synchronized</code>来同步代码块的时候，经编译后，会在代码块的起始位置插入<code>monitorenter</code>指令，在结束或异常处插入<code>monitorexit</code>指令。当执行到<code>monitorenter</code>指令时，将会尝试获取对象所对应的<code>monitor</code>的所有权，即尝试获得对象的锁。而<code>synchronized</code>用的锁是存放在<code>Java对象头</code>中的。</p></blockquote><h3 id="Java-对象头和-Monitor"><a href="#Java-对象头和-Monitor" class="headerlink" title="Java 对象头和 Monitor"></a>Java 对象头和 Monitor</h3><h4 id="Java-对象头"><a href="#Java-对象头" class="headerlink" title="Java 对象头"></a>Java 对象头</h4><blockquote><p>以 Hotspot 虚拟机为例，Hotspot 的对象头主要包括两部分数据：<strong>Mark Word（标记字段）</strong>、<strong>Klass Pointer（类型指针）</strong>。</p></blockquote><ul><li><strong>Mark Word</strong>：默认存储对象的 HashCode，分代年龄和锁标志位信息。这些信息都是与对象自身定义无关的数据，所以 Mark Word 被设计成一个非固定的数据结构以便在极小的空间内存存储尽量多的数据。它会根据对象的状态复用自己的存储空间，也就是说在运行期间 Mark Word 里存储的数据会随着锁标志位的变化而变化。</li><li><strong>Klass Point</strong>：对象指向它的类元数据的指针，虚拟机通过这个指针来确定这个对象是哪个类的实例。</li></ul><h4 id="Monitor"><a href="#Monitor" class="headerlink" title="Monitor"></a>Monitor</h4><ul><li>Monitor 可以理解为一个同步工具或一种同步机制，通常被描述为一个对象。每一个 Java 对象就有一把看不见的锁，称为内部锁或者 Monitor 锁。</li><li>Monitor 是线程私有的数据结构，每一个线程都有一个可用<code>monitor record</code>列表，同时还有一个全局的可用列表。每一个被锁住的对象都会和一个<code>monitor</code>关联，同时<code>monitor</code>中有一个<code>Owner</code>字段存放拥有该锁的线程的<code>唯一标识</code>，表示该锁被这个线程占用。</li></ul><h3 id="偏向锁-1"><a href="#偏向锁-1" class="headerlink" title="偏向锁"></a>偏向锁</h3><h4 id="偏向锁的获取和撤销逻辑"><a href="#偏向锁的获取和撤销逻辑" class="headerlink" title="偏向锁的获取和撤销逻辑"></a>偏向锁的获取和撤销逻辑</h4><ol><li>首先获取锁对象的<code>Markword</code>，判断是否处于<code>可偏向状态</code>。<code>(biased_lock=1、且 ThreadId 为空)</code></li><li>如果是可偏向状态，则通过<code>CAS</code>操作，把当前线程的<code>ID</code>写入到<code>MarkWord</code><ol><li>如果cas成功，那么 markword 就会变成这样。表示已经获得了锁对象的偏向锁，接着执行同步代码块</li><li>如果cas失败，说明有其他线程已经获得了偏向锁， 这种情况说明当前锁存在竞争，需要撤销已获得偏向 锁的线程，并且把它持有的锁升级为轻量级锁（这个操作需要等到全局安全点，也就是没有线程在执行字节码）才能执行</li></ol></li><li>如果是已偏向状态，需要检查 markword 中存储的 ThreadID 是否等于当前线程的 ThreadID a) 如果相等，不需要再次获得锁，可直接执行同步代码 块 b) 如果不相等，说明当前锁偏向于其他线程，需要撤销 偏向锁并升级到轻量级锁</li></ol><h4 id="偏向锁的撤销"><a href="#偏向锁的撤销" class="headerlink" title="偏向锁的撤销"></a>偏向锁的撤销</h4><p>偏向锁的撤销并不是把对象恢复到无锁可偏向状态（因为 偏向锁并不存在锁释放的概念），而是在获取偏向锁的过程中，发现<code>cas</code>失败也就是存在线程竞争时，直接把被偏向的锁对象升级到被加了轻量级锁的状态。</p><p>对原持有偏向锁的线程进行撤销时，原获得偏向锁的线程 有两种情况：</p><ol><li>原获得偏向锁的线程如果已经退出了临界区，也就是同 步代码块执行完了，那么这个时候会把对象头设置成无 锁状态并且争抢锁的线程可以基于 CAS 重新偏向当前线程。</li><li>如果原获得偏向锁的线程的同步代码块还没执行完，处于临界区之内，这个时候会把原获得偏向锁的线程升级 为轻量级锁后继续执行同步代码块。</li></ol><blockquote><p>在我们的应用开发中，绝大部分情况下一定会存在<code>2</code>个以上的线程竞争，那么如果开启偏向锁，反而会提升获取锁的资源消耗。所以可以通过<code>jvm</code>参数<code>UseBiasedLocking</code>来设置开启或关闭偏向锁</p></blockquote><h3 id="轻量级锁-1"><a href="#轻量级锁-1" class="headerlink" title="轻量级锁"></a>轻量级锁</h3><h4 id="轻量级锁的加锁和解锁逻辑"><a href="#轻量级锁的加锁和解锁逻辑" class="headerlink" title="轻量级锁的加锁和解锁逻辑"></a>轻量级锁的加锁和解锁逻辑</h4><p>锁升级为轻量级锁之后，对象的 Markword 也会进行相应 的的变化。升级为轻量级锁的过程：</p><ol><li>线程在自己的栈桢中创建锁记录<code>LockRecord</code>。</li><li>将锁对象的对象头中的MarkWord复制到线程的刚刚创 建的锁记录中。</li><li>将锁记录中的<code>Owner</code>指针指向锁对象。</li><li>将锁对象的对象头的<code>MarkWord</code>替换为指向锁记录的指针。</li></ol><h4 id="轻量级锁的解锁"><a href="#轻量级锁的解锁" class="headerlink" title="轻量级锁的解锁"></a>轻量级锁的解锁</h4><p>轻量级锁的锁释放逻辑其实就是获得锁的逆向逻辑，通过<code>CAS</code>操作把线程栈帧中的<code>LockRecord</code>替换回到锁对象的<code>MarkWord</code>中，如果成功表示没有竞争。如果失败，表示当前锁存在竞争，那么轻量级锁就会膨胀成为重量级锁</p><h3 id="自旋锁-1"><a href="#自旋锁-1" class="headerlink" title="自旋锁"></a>自旋锁</h3><blockquote><p>轻量级锁在加锁过程中，用到了自旋锁</p></blockquote><p>所谓自旋，就是指当有另外一个线程来竞争锁时，这个线 程会在原地循环等待，而不是把该线程给阻塞，直到那个 获得锁的线程释放锁之后，这个线程就可以马上获得锁的。</p><blockquote><p>注意，锁在原地循环的时候，是会消耗 cpu 的，就相当于在执行一个啥也没有的<code>for</code>循环。</p></blockquote><p>所以，轻量级锁适用于那些同步代码块执行的很快的场景，这样，线程原地等待很短的时间就能够获得锁了。 自旋锁的使用，其实也是有一定的概率背景，在大部分同 步代码块执行的时间都是很短的。所以通过看似无异议的 循环反而能提升锁的性能。 但是自旋必须要有一定的条件控制，否则如果一个线程执行同步代码块的时间很长，那么这个线程不断的循环反而 会消耗CPU资源。默认情况下自旋的次数是<code>10</code>次，可以通过<code>preBlockSpin</code> 来修改<br>在 JDK1.6 之后，引入了自适应自旋锁，自适应意味着自旋 的次数不是固定不变的，而是根据前一次在同一个锁上自 旋的时间以及锁的拥有者的状态来决定。 如果在同一个锁对象上，自旋等待刚刚成功获得过锁，并 且持有锁的线程正在运行中，那么虚拟机就会认为这次自 旋也是很有可能再次成功，进而它将允许自旋等待持续相 对更长的时间。<br>如果对于某个锁，自旋很少成功获得过， 那在以后尝试获取这个锁时将可能省略掉自旋过程，直接 阻塞线程，避免浪费处理器资源</p><h3 id="重量级锁"><a href="#重量级锁" class="headerlink" title="重量级锁"></a>重量级锁</h3><p>当轻量级锁膨胀到重量级锁之后，意味着线程只能被挂起 阻塞来等待被唤醒了。</p><h2 id="Tomcat了解么，说一下类加载器结构吧。"><a href="#Tomcat了解么，说一下类加载器结构吧。" class="headerlink" title="Tomcat了解么，说一下类加载器结构吧。"></a>Tomcat了解么，说一下类加载器结构吧。</h2><blockquote><p>以Tomcat6为例</p></blockquote><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/09/08/59faea80-d200-11e9-93b8-c3a852f09de8.png" alt="Tomcat类加载器.png"></p><blockquote><p>Tomcat的类加载机制是<strong>违反了双亲委托原则</strong>的，对于一些未加载的非基础类(<code>Object</code>,<code>String</code>等)，各个web应用自己的类加载器(<code>WebAppClassLoader</code>)会优先加载，加载不到时再交给<code>commonClassLoader</code>走双亲委托。<br>对于JVM来说：按照这个过程可以想到，如果同样在<code>CLASSPATH</code>指定的目录中和自己工作目录中存放<strong>相同的class</strong>，会优先加载<code>CLASSPATH目录</code>中的文件。</p></blockquote><blockquote><p>Q:既然 Tomcat 不遵循双亲委派机制，那么如果我自己定义一个恶意的HashMap，会不会有风险呢？<br>A:显然不会有风险，如果有，Tomcat都运行这么多年了，那群Tomcat大神能不改进吗？ tomcat不遵循双亲委派机制，只是自定义的classLoader顺序不同，但顶层还是相同的，还是要去顶层请求classloader。</p></blockquote><h2 id="Spring中如何让A和B两个bean按顺序加载？"><a href="#Spring中如何让A和B两个bean按顺序加载？" class="headerlink" title="Spring中如何让A和B两个bean按顺序加载？"></a>Spring中如何让A和B两个bean按顺序加载？</h2><h3 id="通过标识位"><a href="#通过标识位" class="headerlink" title="通过标识位"></a>通过标识位</h3><p>我们可以在业务层自己控制A，B的初始化顺序，在A中设置一个<code>是否初始化的</code>标记，B初始化前检测A是否得以初始化，如果没有则调用A的初始化方法，所谓的<code>check-and-act</code>。</p><p>代码实现如下:<br>Bean A：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">A</span> &#123;</span><br><span class="line"> </span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">volatile</span> <span class="type">boolean</span> initialized;</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> B b;</span><br><span class="line"> </span><br><span class="line">  <span class="keyword">public</span> <span class="title function_">A</span><span class="params">()</span> &#123;</span><br><span class="line">    System.out.println(<span class="string">&quot;A construct&quot;</span>);</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">    initA();</span><br><span class="line">  &#125;</span><br><span class="line"> </span><br><span class="line">  <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isInitialized</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> initialized;</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_">initA</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (!isInitialized()) &#123;</span><br><span class="line">      System.out.println(<span class="string">&quot;A init&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    initialized = <span class="literal">true</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>Bean B:</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">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">B</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> A a;</span><br><span class="line"> </span><br><span class="line"> </span><br><span class="line">  <span class="keyword">public</span> <span class="title function_">B</span><span class="params">()</span> &#123;</span><br><span class="line">    System.out.println(<span class="string">&quot;B construct&quot;</span>);</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">    initB();</span><br><span class="line">  &#125;</span><br><span class="line"> </span><br><span class="line"> </span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">initB</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (!a.isInitialized()) &#123;</span><br><span class="line">      a.initA();</span><br><span class="line">    &#125;</span><br><span class="line">    System.out.println(<span class="string">&quot;B init&quot;</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>运行结果</p><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></pre></td><td class="code"><pre><span class="line">A construct</span><br><span class="line">B construct</span><br><span class="line">A init</span><br><span class="line">B init</span><br><span class="line"></span><br></pre></td></tr></table></figure><blockquote><p>这种方法好处是可以做到<code>Lazy initialization（懒加载）</code>，但是如果类似逻辑很多的话代码中到处充斥着类似代码，不优雅，所以考虑是否框架本身就可以满足我们的需要。</p></blockquote><h3 id="使用-DependsOn"><a href="#使用-DependsOn" class="headerlink" title="使用@DependsOn"></a>使用<code>@DependsOn</code></h3><p><code>@DependsOn</code>只是保证的被依赖的bean先于当前bean被实例化，被创建，所以如果要采用这种方式实现bean初始化顺序的控制，那么可以把初始化逻辑放在构造函数中，但是复杂耗时的逻辑仿造构造器中是不合适的，会影响系统启动速度。</p><h3 id="容器加载bean之前"><a href="#容器加载bean之前" class="headerlink" title="容器加载bean之前"></a>容器加载bean之前</h3><p>Spring框架中很多地方都为我们提供了扩展点，很好的体现了<code>开闭原则（OCP）</code>。其中<code>BeanFactoryPostProcessor</code>可以允许我们在容器加载任何bean之前修改应用上下文中的<code>BeanDefinition</code>（从XML配置文件或者配置类中解析得到的bean信息，用于后续实例化bean）。</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></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_">ABeanFactoryPostProcessor</span> <span class="keyword">implements</span> <span class="title class_">BeanFactoryPostProcessor</span> &#123;</span><br><span class="line">  <span class="meta">@Override</span></span><br><span class="line">  <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">postProcessBeanFactory</span><span class="params">(ConfigurableListableBeanFactory configurableListableBeanFactory)</span> <span class="keyword">throws</span> BeansException &#123;</span><br><span class="line">    A.initA();</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><blockquote><p>这种方式把A中的初始化逻辑放到了加载bean之前，很适合加载系统全局配置，但是这种方式中初始化逻辑不能依赖bean的状态。</p></blockquote><h3 id="事件监听器的有序性"><a href="#事件监听器的有序性" class="headerlink" title="事件监听器的有序性"></a>事件监听器的有序性</h3><blockquote><p>Spring 中的<code>Ordered</code>也是一个很重要的组件，很多逻辑中都会判断对象是否实现了<code>Ordered</code>接口，如果实现了就会先进行排序操作。比如在事件发布的时候，对获取到的<code>ApplicationListener</code>会先进行排序。所以可以利用事件监听器在处理事件时的有序性，在应用上下文<code>refresh</code>完成后，分别实现A，B中对应的初始化逻辑。</p></blockquote><p>示例代码如下:</p><p>Bean A:</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">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ApplicationListenerA</span> <span class="keyword">implements</span> <span class="title class_">ApplicationListener</span>&lt;ApplicationContextEvent&gt;, Ordered &#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_">onApplicationEvent</span><span class="params">(ApplicationContextEvent event)</span> &#123;</span><br><span class="line">    initA();</span><br><span class="line">  &#125;</span><br><span class="line"> </span><br><span class="line">  <span class="meta">@Override</span></span><br><span class="line">  <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">getOrder</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> Ordered.HIGHEST_PRECEDENCE; <span class="comment">// 比 ApplicationListenerB 优先级高</span></span><br><span class="line">  &#125;</span><br><span class="line"> </span><br><span class="line">  <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">initA</span><span class="params">()</span> &#123;</span><br><span class="line">    System.out.println(<span class="string">&quot;A init&quot;</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"> </span><br></pre></td></tr></table></figure><p>Bean B:</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">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ApplicationListenerB</span> <span class="keyword">implements</span> <span class="title class_">ApplicationListener</span>&lt;ApplicationContextEvent&gt;, Ordered&#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_">onApplicationEvent</span><span class="params">(ApplicationContextEvent event)</span> &#123;</span><br><span class="line">    initB();</span><br><span class="line">  &#125;</span><br><span class="line"> </span><br><span class="line">  <span class="meta">@Override</span></span><br><span class="line">  <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">getOrder</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> Ordered.HIGHEST_PRECEDENCE -<span class="number">1</span>;</span><br><span class="line">  &#125;</span><br><span class="line"> </span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">initB</span><span class="params">()</span> &#123;</span><br><span class="line">    System.out.println(<span class="string">&quot;B init&quot;</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><blockquote><p>这种方式就是站在事件响应的角度，上下文加载完成后，先实现A逻辑，然后实现B逻辑。</p></blockquote><h2 id="海量数据去重"><a href="#海量数据去重" class="headerlink" title="海量数据去重"></a>海量数据去重</h2><blockquote><p>Q:10亿个数去重<br>A:用hash分片做<br>Q:可能不均匀<br>A:用<code>Bitmap</code><br>Q:数字量更多怎么办<br>A:用两个bitmap<br><strong>注</strong>：这题面试者的回答并没有让面试官满意。我们也就这个问题做个讨论，结果做个参考。</p></blockquote><ul><li>如果允许使用中间件，且硬件条件允许的情况下可以使用<code>MapReduce+HDFS</code>,参考<a href="https://cloud.tencent.com/developer/article/1187611">巧用MapReduce+HDFS，海量数据去重的五大策略</a><ul><li>只使用HDFS和MapReduce</li><li>使用HDFS和Hbase</li><li>使用HDFS，MapReduce和存储控制器</li><li>使用Streaming，HDFS，MapReduce</li><li>结合块技术使用MapReduce</li></ul></li><li>参考<a href="https://cloud.tencent.com/developer/article/1121843">海量数据去重之SimHash算法简介和应用</a></li><li>如果再在物理内存上加了限制，就需要考虑写入文件了</li></ul><h1 id="二面-技术面"><a href="#二面-技术面" class="headerlink" title="二面(技术面)"></a>二面(技术面)</h1><h2 id="讲一下项目。"><a href="#讲一下项目。" class="headerlink" title="讲一下项目。"></a><del>讲一下项目。</del></h2><blockquote><p>同上，多说些简历上没有的。</p></blockquote><h2 id="讲一下多线程把，用到哪些写一下。"><a href="#讲一下多线程把，用到哪些写一下。" class="headerlink" title="讲一下多线程把，用到哪些写一下。"></a>讲一下多线程把，用到哪些写一下。</h2><h3 id="基本线程类"><a href="#基本线程类" class="headerlink" title="基本线程类"></a>基本线程类</h3><blockquote><p>基本线程类指的是<code>Thread类</code>，<code>Runnable接口</code>，<code>Callable接口</code></p></blockquote><h3 id="高级多线程控制类"><a href="#高级多线程控制类" class="headerlink" title="高级多线程控制类"></a>高级多线程控制类</h3><blockquote><p><code>ThreadLocal类</code>,<code>java.util.concurrent.atomic包下实现的Automic类</code>,<code>Lock类</code>,<code>容器类(BlockingQueue,ConcurrentHashMap等)</code>,<code>ThreadPoolExecutor(线程池)</code></p></blockquote><h2 id="线程池由哪些组件组成，有哪些线程池，分别怎么使用，以及拒绝策略有哪些。"><a href="#线程池由哪些组件组成，有哪些线程池，分别怎么使用，以及拒绝策略有哪些。" class="headerlink" title="线程池由哪些组件组成，有哪些线程池，分别怎么使用，以及拒绝策略有哪些。"></a>线程池由哪些组件组成，有哪些线程池，分别怎么使用，以及拒绝策略有哪些。</h2><h3 id="线程池由哪些些组件组成"><a href="#线程池由哪些些组件组成" class="headerlink" title="线程池由哪些些组件组成"></a>线程池由哪些些组件组成</h3><ol><li><strong>线程池管理器（ThreadPoolManager）</strong>:用于创建并管理线程池</li><li><strong>工作线程（WorkThread）</strong>: 线程池中线程</li><li><strong>任务接口（Task）</strong>:每个任务必须实现的接口，以供工作线程调度任务的执行。</li><li><strong>任务队列</strong>:用于存放没有处理的任务。提供一种缓冲机制。</li></ol><h3 id="有哪些线程池以及使用场景"><a href="#有哪些线程池以及使用场景" class="headerlink" title="有哪些线程池以及使用场景"></a>有哪些线程池以及使用场景</h3><blockquote><p>具体代码请查阅API或者Google</p></blockquote><ol><li><strong>newCachedThreadPool</strong>：创建一个可缓存线程池程。是一种线程数量不定的线程池，并且其最大线程数为<code>Integer.MAX_VALUE</code>，这个数是很大的，一个可缓存线程池，如果线程池长度超过处理需要，可灵活回收空闲线程，若无可回收，则新建线程。但是线程池中的空闲线程都有超时限制，这个超时时长是60秒，超过60秒闲置线程就会被回收。调用<code>execute</code>将重用以前构造的线程(如果线程可用)。这类线程池比较适合执行大量的耗时较少的任务，当整个线程池都处于闲置状态时，线程池中的线程都会超时被停止。</li><li><strong>newFixedThreadPool</strong>：创建一个定长线程池。每当提交一个任务就创建一个工作线程，当线程 处于空闲状态时，它们并不会被回收，除非线程池被关闭了，如果工作线程数量达到线程池初始的最大数，则将提交的任务存入到池队列（没有大小限制）中。由于newFixedThreadPool只有核心线程并且这些核心线程不会被回收，这样它更加快速底相应外界的请求。</li><li><strong>newScheduledThreadPool</strong>：创建一个定长线程池。它的核心线程数量是固定的，而非核心线程数是没有限制的，并且当非核心线程闲置时会被立即回收，它可安排给定延迟后运行命令或者定期地执行。这类线程池主要用于执行定时任务和具有固定周期的重复任务。</li><li><strong>newSingleThreadExecutor</strong>：创建一个单线程化的线程池。这类线程池内部只有一个核心线程，以无界队列方式来执行该线程，这使得这些任务之间不需要处理线程同步的问题，它确保所有的任务都在同一个线程中按顺序中执行，并且可以在任意给定的时间不会有多个线程是活动的。</li></ol><h3 id="拒绝策略有哪些"><a href="#拒绝策略有哪些" class="headerlink" title="拒绝策略有哪些"></a>拒绝策略有哪些</h3><blockquote><p>当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize时，如果还有任务到来就会采取任务拒绝策略。</p></blockquote><p>JDK自带的拒绝策略</p><ol><li><code>ThreadPoolExecutor.AbortPolicy</code>:丢弃任务并抛出RejectedExecutionException异常。</li><li><code>ThreadPoolExecutor.DiscardPolicy</code>：丢弃任务，但是不抛出异常。</li><li><code>ThreadPoolExecutor.DiscardOldestPolicy</code>：丢弃队列最前面的任务，然后重新提交被拒绝的任务</li><li><code>ThreadPoolExecutor.CallerRunsPolicy</code>：由调用线程（提交任务的线程）处理该任务</li></ol><p>第三方的拒绝策略</p><ul><li>Dubbo<ul><li>输出了一条警告级别的日志，日志内容为线程池的详细设置参数，以及线程池当前的状态，还有当前拒绝任务的一些详细信息。可以说，这条日志，使用dubbo的有过生产运维经验的或多或少是见过的，这个日志简直就是日志打印的典范，其他的日志打印的典范还有spring。得益于这么详细的日志，可以很容易定位到问题所在。</li><li>输出当前线程堆栈详情，这个太有用了，当你通过上面的日志信息还不能定位问题时，案发现场的dump线程上下文信息就是你发现问题的救命稻草，这个可以参考<code>《dubbo线程池耗尽事件-&quot;CyclicBarrier惹的祸&quot;》</code></li><li>继续抛出拒绝执行异常，使本次任务失败，这个继承了JDK默认拒绝策略的特性</li></ul></li><li>Netty<br>类似JDK 中的 CallerRunsPolicy，舍不得丢弃任务。不同的是，CallerRunsPolicy 是直接在调用者线程执行的任务。而 Netty是新建了一个线程来处理的。所以，Netty的实现相较于调用者执行策略的使用面就可以扩展到支持高效率高性能的场景了。但是也要注意一点，Netty的实现里，在创建线程时未做任何的判断约束，也就是说只要系统还有资源就会创建新的线程来处理，直到new不出新的线程了，才会抛创建线程失败的异常</li><li>activeMq<br>最大努力执行任务型，当触发拒绝策略时，在尝试一分钟的时间重新将任务塞进任务队列，当一分钟超时还没成功时，就抛出异常</li><li>PinPoint<br>和其他的实现都不同。他定义了一个拒绝策略链，包装了一个拒绝策略列表，当触发拒绝策略时，会将策略链中的rejectedExecution依次执行一遍。</li></ul><h2 id="什么时候多线程会发生死锁，写一个例子吧"><a href="#什么时候多线程会发生死锁，写一个例子吧" class="headerlink" title="什么时候多线程会发生死锁，写一个例子吧"></a>什么时候多线程会发生死锁，写一个例子吧</h2><h3 id="什么时候多线程会发生死锁"><a href="#什么时候多线程会发生死锁" class="headerlink" title="什么时候多线程会发生死锁"></a>什么时候多线程会发生死锁</h3><h4 id="死锁产生的原因"><a href="#死锁产生的原因" class="headerlink" title="死锁产生的原因"></a>死锁产生的原因</h4><ol><li>系统资源的竞争<br> 通常系统中拥有的不可剥夺资源，其数量不足以满足多个进程运行的需要，使得进程在 运行过程中，会因争夺资源而陷入僵局，如磁带机、打印机等。只有对不可剥夺资源的竞争 才可能产生死锁，对可剥夺资源的竞争是不会引起死锁的。</li><li>进程推进顺序非法<br> 进程在运行过程中，请求和释放资源的顺序不当，也同样会导致死锁。例如，并发进程 P1、P2分别保持了资源R1、R2，而进程P1申请资源R2，进程P2申请资源R1时，两者都 会因为所需资源被占用而阻塞。<br>信号量使用不当也会造成死锁。进程间彼此相互等待对方发来的消息，结果也会使得这 些进程间无法继续向前推进。例如，进程A等待进程B发的消息，进程B又在等待进程A 发的消息，可以看出进程A和B不是因为竞争同一资源，而是在等待对方的资源导致死锁。</li><li>死锁产生的必要条件<br> 产生死锁必须同时满足以下四个条件，只要其中任一条件不成立，死锁就不会发生。<ol><li>互斥条件：进程要求对所分配的资源（如打印机）进行排他性控制，即在一段时间内某 资源仅为一个进程所占有。此时若有其他进程请求该资源，则请求进程只能等待。</li><li>不剥夺条件：进程所获得的资源在未使用完毕之前，不能被其他进程强行夺走，即只能 由获得该资源的进程自己来释放（只能是主动释放)。</li><li>请求和保持条件：进程已经保持了至少一个资源，但又提出了新的资源请求，而该资源 已被其他进程占有，此时请求进程被阻塞，但对自己已获得的资源保持不放。</li><li>循环等待条件：存在一种进程资源的循环等待链，链中每一个进程已获得的资源同时被 链中下一个进程所请求。即存在一个处于等待状态的进程集合{Pl, P2, …, pn}，其中Pi等 待的资源被P(i+1)占有（i&#x3D;0, 1, …, n-1)，Pn等待的资源被P0占有</li></ol></li></ol><h4 id="死锁代码示例"><a href="#死锁代码示例" class="headerlink" title="死锁代码示例"></a>死锁代码示例</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><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></pre></td><td class="code"><pre><span class="line"><span class="comment">/** </span></span><br><span class="line"><span class="comment"> * 一个简单的死锁类 </span></span><br><span class="line"><span class="comment"> * 当DeadLock类的对象flag==1时（td1），先锁定o1,睡眠500毫秒 </span></span><br><span class="line"><span class="comment"> * 而td1在睡眠的时候另一个flag==0的对象（td2）线程启动，先锁定o2,睡眠500毫秒 </span></span><br><span class="line"><span class="comment"> * td1睡眠结束后需要锁定o2才能继续执行，而此时o2已被td2锁定； </span></span><br><span class="line"><span class="comment"> * td2睡眠结束后需要锁定o1才能继续执行，而此时o1已被td1锁定； </span></span><br><span class="line"><span class="comment"> * td1、td2相互等待，都需要得到对方锁定的资源才能继续执行，从而死锁。 </span></span><br><span class="line"><span class="comment"> */</span>  </span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DeadLock</span> <span class="keyword">implements</span> <span class="title class_">Runnable</span> &#123;  </span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="variable">flag</span> <span class="operator">=</span> <span class="number">1</span>;  </span><br><span class="line">    <span class="comment">//静态对象是类的所有对象共享的  </span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="type">Object</span> <span class="variable">o1</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Object</span>();</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="type">Object</span> <span class="variable">o2</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Object</span>();   </span><br><span class="line">    <span class="meta">@Override</span>  </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;  </span><br><span class="line">        System.out.println(<span class="string">&quot;flag=&quot;</span> + flag);  </span><br><span class="line">        <span class="keyword">if</span> (flag == <span class="number">1</span>) &#123;  </span><br><span class="line">            <span class="keyword">synchronized</span> (o1) &#123;  </span><br><span class="line">                <span class="keyword">try</span> &#123;  </span><br><span class="line">                    Thread.sleep(<span class="number">500</span>);  </span><br><span class="line">                &#125; <span class="keyword">catch</span> (Exception e) &#123;  </span><br><span class="line">                    e.printStackTrace();  </span><br><span class="line">                &#125;  </span><br><span class="line">                <span class="keyword">synchronized</span> (o2) &#123;  </span><br><span class="line">                    System.out.println(<span class="string">&quot;1&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">        <span class="keyword">if</span> (flag == <span class="number">0</span>) &#123;  </span><br><span class="line">            <span class="keyword">synchronized</span> (o2) &#123;  </span><br><span class="line">                <span class="keyword">try</span> &#123;  </span><br><span class="line">                    Thread.sleep(<span class="number">500</span>);  </span><br><span class="line">                &#125; <span class="keyword">catch</span> (Exception e) &#123;  </span><br><span class="line">                    e.printStackTrace();  </span><br><span class="line">                &#125;  </span><br><span class="line">                <span class="keyword">synchronized</span> (o1) &#123;  </span><br><span class="line">                    System.out.println(<span class="string">&quot;0&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">    &#125;  </span><br><span class="line">  </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;  </span><br><span class="line">          </span><br><span class="line">        <span class="type">DeadLock</span> <span class="variable">td1</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DeadLock</span>();  </span><br><span class="line">        <span class="type">DeadLock</span> <span class="variable">td2</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">DeadLock</span>();  </span><br><span class="line">        td1.flag = <span class="number">1</span>;  </span><br><span class="line">        td2.flag = <span class="number">0</span>;  </span><br><span class="line">        <span class="comment">//td1,td2都处于可执行状态，但JVM线程调度先执行哪个线程是不确定的。  </span></span><br><span class="line">        <span class="comment">//td2的run()可能在td1的run()之前运行  </span></span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">Thread</span>(td1).start();  </span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">Thread</span>(td2).start();  </span><br><span class="line">  </span><br><span class="line">    &#125;  </span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="一个字符串集合，找出pdd并且删除。"><a href="#一个字符串集合，找出pdd并且删除。" class="headerlink" title="一个字符串集合，找出pdd并且删除。"></a>一个字符串集合，找出pdd并且删除。</h2><blockquote><p>皮一下，面试官是不是和pdd有什么过节？</p></blockquote><p>这个题可延伸部分很多，主要注意以下几点：</p><ol><li>要考虑保证线程安全</li><li>使用迭代器迭代集合内元素时，不能删除集合内元素</li></ol><h2 id="Redis是单线程还是多线程，Redis的分布式怎么做？"><a href="#Redis是单线程还是多线程，Redis的分布式怎么做？" class="headerlink" title="Redis是单线程还是多线程，Redis的分布式怎么做？"></a>Redis是单线程还是多线程，Redis的分布式怎么做？</h2><h3 id="Redis是单线程还是多线程"><a href="#Redis是单线程还是多线程" class="headerlink" title="Redis是单线程还是多线程"></a>Redis是单线程还是多线程</h3><blockquote><p>Redis是单线程的，这样设计省去了上下文切换的性能与时间消耗</p></blockquote><h3 id="Redis的分布式怎么做"><a href="#Redis的分布式怎么做" class="headerlink" title="Redis的分布式怎么做"></a>Redis的分布式怎么做</h3><blockquote><p>这里的解决方案很多，但是既然使用了分布式，那么Redis也就必须使用集群，避免单点问题。<br>在使用Redis做分布式时，Redis不仅仅能充当缓存，还可以基于Redis实现分布式锁。<br>更多解决方案细节自行百度或者Google</p></blockquote><h2 id="RPC了解么"><a href="#RPC了解么" class="headerlink" title="RPC了解么"></a>RPC了解么</h2><blockquote><p>面试者回答：主要是<code>协议栈</code>+<code>数据格式</code>+<code>序列化方式</code>，然后需要有<code>服务注册中心</code>管理<code>生产者</code>和<code>消费者</code>。</p></blockquote><p>详细参考<a href="https://my.oschina.net/hosee/blog/711632">RPC原理及RPC实例分析</a></p><h2 id="TCP三次握手的过程，如果没有第三次握手有什么问题。"><a href="#TCP三次握手的过程，如果没有第三次握手有什么问题。" class="headerlink" title="TCP三次握手的过程，如果没有第三次握手有什么问题。"></a>TCP三次握手的过程，如果没有第三次握手有什么问题。</h2><blockquote><p>在回答这个问题前首先我们要了解<strong>TCP连接的建立与释放（三次握手、四次挥手）</strong></p></blockquote><h3 id="TCP协议相关知识"><a href="#TCP协议相关知识" class="headerlink" title="TCP协议相关知识"></a>TCP协议相关知识</h3><h4 id="TCP协议简介"><a href="#TCP协议简介" class="headerlink" title="TCP协议简介"></a>TCP协议简介</h4><blockquote><p>TCP是面向连接的运输层协议，它提供可靠交付的、全双工的、面向字节流的点对点服务。HTTP协议便是基于TCP协议实现的。（虽然作为应用层协议，HTTP协议并没有明确要求必须使用TCP协议作为运输层协议，但是因为HTTP协议对可靠性的的要求，默认HTTP是基于TCP协议的。若是使用UDP这种不可靠的、尽最大努力交付的运输层协议来实现HTTP的话，那么TCP协议的流量控制、可靠性保障机制等等功能就必须全部放到应用层来实现）而相比网络层更进一步，运输层着眼于应用进程间的通信，而不是网络层的主机间的通讯。我们常见的端口、套接字等概念就是由此而生。（端口代表主机上的一个应用进程、而套接字则是ip地址与端口号的合体，可以在网络范围内唯一确定一个应用进程） TCP协议的可靠传输是通过滑动窗口的方法实现的；拥塞控制则有着慢开始和拥塞避免、快重传和快恢复、RED随机早期检测几种办法。</p></blockquote><p>报文格式参考</p><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/09/08/be0b4c10-d20d-11e9-93b8-c3a852f09de8.png" alt="TCP报文格式.png"></p><p>脑图参考</p><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/09/08/42988e20-d20e-11e9-93b8-c3a852f09de8.png" alt="TCP报文首部.png"></p><p> TCP报文段的首部分为固定部分和选项部分，固定部分长20byte，而选项部分长度可变。（若整个首部长度不是4byte的整数倍的话，则需要用填充位来填充）在固定首部中，与本文密切相关的是以下几项：</p><ul><li><strong>seq（序号）</strong>：TCP连接字节流中每一个字节都会有一个编号，而本字段的值指的是本报文段所发送数据部分第一个字节的序号。</li><li><strong>ack（确认号）</strong>：表示期望收到的下一个报文段数据部分的第一个字节的编号，编号为ack-1及以前的字节已经收到。</li><li><strong>SYN</strong>：当本字段为1时，表示这是一个连接请求或者连接接受报文。</li><li><strong>ACK</strong>：仅当本字段为1时，确认号才有效。</li><li><strong>FIN</strong>：用来释放一个连接。当本字段为1时，表示此报文段的发送端数据已发送完毕，要求释放运输连接。</li></ul><h4 id="TCP的运输连接管理"><a href="#TCP的运输连接管理" class="headerlink" title="TCP的运输连接管理"></a>TCP的运输连接管理</h4><blockquote><p>运输连接具有三个阶段：连接建立、数据传送以及连接释放。运输连接管理就是对连接建立以及连接释放过程的管控，使得其能正常运行，达到这些目的：使通信双方能够确知对方的存在、可以允许通信双方协商一些参数（最大报文段长度、最大窗口大小等等）、能够对运输实体资源进行分配（缓存大小等）。TCP连接的建立采用客户-服务器模式：主动发起连接建立的应用进程叫做客户，被动等待连接建立的应用进程叫做服务器。</p></blockquote><p><strong>连接建立阶段</strong></p><ol><li><strong>第一次握手</strong>：客户端的应用进程主动打开，并向客户端发出请求报文段。其首部中：<code>SYN=1,seq=x</code>。</li><li><strong>第二次握手</strong>：服务器应用进程被动打开。若同意客户端的请求，则发回确认报文，其首部中：<code>SYN=1,ACK=1,ack=x+1,seq=y</code>。</li><li><strong>第三次握手</strong>：客户端收到确认报文之后，通知上层应用进程连接已建立，并向服务器发出确认报文，其首部：<code>ACK=1,ack=y+1</code>。当服务器收到客户端的确认报文之后，也通知其上层应用进程连接已建立。</li></ol><blockquote><p>在这个过程中，通信双方的状态如下图，其中CLOSED：关闭状态、LISTEN：收听状态、SYN-SENT：同步已发送、SYN-RCVD：同步收到、ESTAB-LISHED：连接已建立。</p></blockquote><p><strong>连接释放阶段</strong></p><ol><li>第一次挥手：数据传输结束以后，客户端的应用进程发出连接释放报文段，并停止发送数据，其首部：<code>FIN=1,seq=u</code>。</li><li>第二次挥手：服务器端收到连接释放报文段之后，发出确认报文，其首部：<code>ack=u+1,seq=v</code>。此时本次连接就进入了半关闭状态，客户端不再向服务器发送数据。而服务器端仍会继续发送。</li><li>第三次挥手：若服务器已经没有要向客户端发送的数据，其应用进程就通知服务器释放TCP连接。这个阶段服务器所发出的最后一个报文的首部应为：<code>FIN=1,ACK=1,seq=w,ack=u+1</code>。</li><li>第四次挥手：客户端收到连接释放报文段之后，必须发出确认：<code>ACK=1,seq=u+1,ack=w+1</code>。 再经过<code>2MSL(最长报文端寿命)</code>后，本次TCP连接真正结束，通信双方完成了他们的告别。</li></ol><blockquote><p>在这个过程中，通信双方的状态如下图，其中：</p><ul><li>ESTAB-LISHED：连接建立状态</li><li>FIN-WAIT-1：终止等待1状态</li><li>FIN-WAIT-2：终止等待2状态</li><li>CLOSE-WAIT：关闭等待状态</li><li>LAST-ACK：最后确认状态</li><li>TIME-WAIT：时间等待状态</li><li>CLOSED：关闭状态</li></ul></blockquote><p>完整过程如图所示：</p><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/09/08/5f705310-d20f-11e9-93b8-c3a852f09de8.png" alt="TCP三次握手四次挥手.png"></p><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/09/08/93f4ff00-d20f-11e9-93b8-c3a852f09de8.png" alt="TCP三次握手四次挥手.png"></p><blockquote><p>Q:在握手与挥手的过程中，往复的ack与seq有什么含义？<br>A:这是通信双方在通信过程中的一种确认手段，确保通信双方通信的正确性。例如小时候模仿电视剧里无线电呼叫的过程：<br>    - <code>土豆土豆，我是地瓜，你能听到吗？</code><br>    - <code>地瓜地瓜，我是土豆，我能听到。</code><br>    若客户端的报文请求号为<code>土豆</code>，则服务器端就将返回确认号<code>土豆+1</code>（标志土豆已收到），是一种通信双方的确认手段。</p></blockquote><blockquote><p>Q:在结束连接的过程中，为什么在收到服务器端的连接释放报文段之后，客户端还要继续等待2MSL之后才真正关闭TCP连接呢？<br>A:这里有两个原因</p><ol><li>需要保证服务器端收到了客户端的最后一条确认报文。假如这条报文丢失，服务器没有接收到确认报文，就会对连接释放报文进行超时重传，而此时客户端连接已关闭，无法做出响应，就造成了服务器端不停重传连接释放报文，而无法正常进入关闭状态的状况。而等待2MSL，就可以保证服务器端收到了最终确认；若服务器端没有收到，那么在2MSL之内客户端一定会收到服务器端的重传报文，此时客户端就会重传确认报文，并重置计时器。<br>   2.存在一种<strong>已失效的连接请求报文段</strong>，需要避免这种报文端出现在本连接中，造成异常。 这种“已失效的连接请求报文段”是这么形成的：假如客户端发出了连接请求报文，然而服务器端没有收到，于是客户端进行超时重传，再一次发送了连接请求报文，并成功建立连接。然而，第一次发送的连接请求报文并没有丢失，只是在某个网络结点中发生了长时间滞留，随后，这个最初发送的报文段到达服务器端，会使得服务器端误以为客户端发出了新的请求，造成异常。</li></ol></blockquote><blockquote><p>Q:若通信双方同时请求连接或同时请求释放连接，情况如何？<br>A:这种情况虽然发生的可能性极小，但是是确实存在的，TCP也特意设计了相关机制，使得在这种情况下双方仅建立一条连接。双方同时请求连接的情况下，双方同时发出请求连接报文，并进入<code>SYN-SENT</code>状态；当收到对方的请求连接报文后，会再次发送请求连接报文，确认号为对方的<code>SYN+1</code>，并进入<code>SYN-RCVD</code>状态；当收到对方第二次发出的携带确认号的请求报文之后，会进入<code>ESTAB-LISHED</code>状态。 双方同时请求释放连接也是同样的，双方同时发出连接释放报文，并进入<code>FIN-WAIT-1</code>状态；在收到对方的报文之后，发送确认报文，并进入<code>CLOSING</code>状态；在收到对方的确认报文后，进入<code>TIME-WAIT</code>状态，等待2MSL之后关闭连接。需要注意的是，这个时候虽然不用再次发送确认报文并确认对方收到，双方仍需等待2MSL之后再关闭连接，是为了防止<strong>已失效的连接请求报文段</strong>的影响。</p></blockquote><h3 id="如果没有第三次握手有什么问题"><a href="#如果没有第三次握手有什么问题" class="headerlink" title="如果没有第三次握手有什么问题"></a>如果没有第三次握手有什么问题</h3><blockquote><p>没有第三步，那就是服务器发送给客户端的<code>SYN</code>没有收到回应，其实这就是<strong>SYN洪泛攻击</strong>。<br>感兴趣可以自己百度或者Google一下，这里不做过多解释。</p></blockquote><p>SYN攻击属于DDOS攻击的一种，它利用TCP协议缺陷，通过发送大量的半连接请求，耗费CPU和内存资源。<br>SYN攻击除了能影响主机外，还可以危害路由器、防火墙等网络系统，事实上SYN攻击并不管目标是什么系统，只要这些系统打开TCP服务就可以实施。<br>服务器接收到连接请求<code>(syn=j)</code>，将此信息加入未连接队列，并发送请求包给客户<code>(syn=k,ack=j+1)</code>，此时进入<code>SYN_RECV</code>状态。当服务器未收到客户端的确认包时，重发请求包，一直到超时，才将此条目从未连接队列删除。配合IP欺骗，SYN攻击能达到很好的效果，通常，客户端在短时间内伪造大量不存在的IP地址，向服务器不断地发送syn包，服务器回复确认包，并等待客户的确认，由于源地址是不存在的，服务器需要不断的重发直至超时，这些伪造的SYN包将长时间占用未连接队列，正常的SYN请求被丢弃，目标系统运行缓慢，严重者引起网络堵塞甚至系统瘫痪。</p><h1 id="三面-技术面"><a href="#三面-技术面" class="headerlink" title="三面(技术面)"></a>三面(技术面)</h1><h2 id="自我介绍"><a href="#自我介绍" class="headerlink" title="自我介绍"></a><del>自我介绍</del></h2><blockquote><p>已经麻木，进入背稿阶段~</p></blockquote><h2 id="cap了解么，分别指什么，base呢，强一致性和弱一致性有什么方法来做，2pc了解么，说一下大概过程。"><a href="#cap了解么，分别指什么，base呢，强一致性和弱一致性有什么方法来做，2pc了解么，说一下大概过程。" class="headerlink" title="cap了解么，分别指什么，base呢，强一致性和弱一致性有什么方法来做，2pc了解么，说一下大概过程。"></a>cap了解么，分别指什么，base呢，强一致性和弱一致性有什么方法来做，2pc了解么，说一下大概过程。</h2><h3 id="cap分别指什么"><a href="#cap分别指什么" class="headerlink" title="cap分别指什么"></a>cap分别指什么</h3><blockquote><p>这是在分布式系统中的概念，内容是以下三个要素最多只能同时实现两点，不可能三者兼顾。</p></blockquote><ul><li>Consistency（一致性）</li><li>Availability（可用性）</li><li>Partition tolerance（分区容错性）</li></ul><h3 id="BASE理论"><a href="#BASE理论" class="headerlink" title="BASE理论"></a>BASE理论</h3><blockquote><p>BASE是<code>Basically Available（基本可用）</code>、<code>Soft state（软状态）</code>和<code>Eventually consistent（最终一致性）</code>的简写。BASE是对CAP中一致性和可用性权衡的结果，契合性思想是即使无法做到强一致性，但每个应用都可以根据自身的业务特点，采用适当的方式来使得系统达到<strong>最终一致性</strong>。</p></blockquote><ol><li>基本可用<br> 基本可用是指分布式系统在出现不可预知的故障的时候，允许损失部分可用性。 <ol><li>相应时间上的损失：正常情况下，一个在线搜索引擎需要在0.5秒之内返回给用户的查询结果，但由于出现故障，查询结果的响应时间增加到1-2秒。<br> 2.功能上的损失：正常情况下，在一个电子商务网站上进行购物，消费者几乎能够顺利的完成每一笔订单，但是在一些节日大促购物高峰的时候（比如双十一），由于消费者的购物行为激增，为了保护购物系统的稳定性，部分消费者可能会被引导到一个降级页面。</li></ol></li><li>弱状态<br> 弱状态也称为软状态，和硬状态相对应，是指允许系统中的数据存在中间状态，并认为该中间状态的存在不会影响系统的整体可用性，即允许系统在不同的节点之间的数据副本进行数据的同步过程存在延迟。</li><li>最终一致性<br> 最终一致性强调的是系统中所有的数据副本，在经过一段时间的同步后，最终能够达到一个一致的状态。因此，最终一致性的本质就是需要系统最终数据达到一致，而不需要实时保证系统数据的强一致性。在没有发生故障的前提下，数据达到一致状态的时间延迟，取决于网络延迟、系统负载和数据复制方案设计等因素。<br>在时间工程实践中，最终一致性存在以下五类主要变种。 <ul><li>因果一致性 </li><li>读己之所写 </li><li>会话一致性 </li><li>单调读一致性 </li><li>单调写一致性</li></ul></li></ol><h3 id="强一致性和弱一致性有什么方法来做"><a href="#强一致性和弱一致性有什么方法来做" class="headerlink" title="强一致性和弱一致性有什么方法来做"></a>强一致性和弱一致性有什么方法来做</h3><h4 id="强一致性"><a href="#强一致性" class="headerlink" title="强一致性"></a>强一致性</h4><h5 id="两阶段提交（2PC）"><a href="#两阶段提交（2PC）" class="headerlink" title="两阶段提交（2PC）"></a>两阶段提交（2PC）</h5><blockquote><p>两阶段提交协议把分布式事务分成两个过程，一个是准备阶段，一个是提交阶段，准备阶段和提交阶段都是由事务管理器发起的，为了接下来讲解方便，我们把事务管理器称为协调者，把资管管理器称为参与者。</p></blockquote><ul><li><strong>准备阶段</strong>：协调者向参与者发起指令，参与者评估自己的状态，如果参与者评估指令可以完成，参与者会写redo或者undo日志（这也是前面提起的<code>Write-Ahead Log</code>的一种），然后锁定资源，执行操作，但是并不提交。</li><li><strong>提交阶段</strong>：如果每个参与者明确返回准备成功，也就是预留资源和执行操作成功，协调者向参与者发起提交指令，参与者提交资源变更的事务，释放锁定的资源；如果任何一个参与者明确返回准备失败，也就是预留资源或者执行操作失败，协调者向参与者发起中止指令，参与者取消已经变更的事务，执行undo日志，释放锁定的资源。</li></ul><p>两阶段提交事务提交示意图：</p><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/09/08/fcb973c0-d211-11e9-93b8-c3a852f09de8.png" alt="两阶段提交事务提交示意图.png"></p><p>两阶段提交事务回滚示意图：</p><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/09/08/523e4e10-d212-11e9-93b8-c3a852f09de8.png" alt="两阶段提交事务回滚示意图.png"></p><p>我们看到两阶段提交协议在准备阶段锁定资源，是一个重量级的操作，并能保证强一致性，但是实现起来复杂、成本较高，不够灵活，更重要的是它有如下致命的问题：</p><ul><li><strong>阻塞</strong>：从上面的描述来看，对于任何一次指令必须收到明确的响应，才会继续做下一步，否则处于阻塞状态，占用的资源被一直锁定，不会被释放。</li><li><strong>单点故障</strong>：如果协调者宕机，参与者没有了协调者指挥，会一直阻塞，尽管可以通过选举新的协调者替代原有协调者，但是如果之前协调者在发送一个提交指令后宕机，而提交指令仅仅被一个参与者接受，并且参与者接收后也宕机，新上任的协调者无法处理这种情况。</li><li><strong>脑裂</strong>：协调者发送提交指令，有的参与者接收到执行了事务，有的参与者没有接收到事务，就没有执行事务，多个参与者之间是不一致的。</li></ul><blockquote><p>上面所有的这些问题，都是需要人工干预处理，没有自动化的解决方案，因此两阶段提交协议在正常情况下能保证系统的强一致性，但是在出现异常情况下，当前处理的操作处于错误状态，需要管理员人工干预解决，因此可用性不够好，这也符合CAP协议的一致性和可用性不能兼得的原理。</p></blockquote><h5 id="三阶段提交-3pc"><a href="#三阶段提交-3pc" class="headerlink" title="三阶段提交(3pc)"></a>三阶段提交(3pc)</h5><blockquote><p>三阶段提交协议是两阶段提交协议的改进版本。它通过超时机制解决了阻塞的问题，并且把两个阶段增加为三个阶段。</p></blockquote><ul><li><strong>询问阶段</strong>：协调者询问参与者是否可以完成指令，协调者只需要回答是还是不是，而不需要做真正的操作，这个阶段超时导致中止。</li><li><strong>准备阶段</strong>：如果在询问阶段所有的参与者都返回可以执行操作，协调者向参与者发送预执行请求，然后参与者写redo和undo日志，执行操作，但是不提交操作；如果在询问阶段任何参与者返回不能执行操作的结果，则协调者向参与者发送中止请求，这里的逻辑与两阶段提交协议的的准备阶段是相似的，这个阶段超时导致成功。</li><li><strong>提交阶段</strong>：如果每个参与者在准备阶段返回准备成功，也就是预留资源和执行操作成功，协调者向参与者发起提交指令，参与者提交资源变更的事务，释放锁定的资源；如果任何一个参与者返回准备失败，也就是预留资源或者执行操作失败，协调者向参与者发起中止指令，参与者取消已经变更的事务，执行undo日志，释放锁定的资源，这里的逻辑与两阶段提交协议的提交阶段一致。</li></ul><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/09/08/6b749ec0-d212-11e9-93b8-c3a852f09de8.png" alt="三阶段提交示意图.png"></p><p>然而，这里与两阶段提交协议有两个主要的不同：</p><ul><li>增加了一个询问阶段，询问阶段可以确保尽可能早的发现无法执行操作而需要中止的行为，但是它并不能发现所有的这种行为，只会减少这种情况的发生。</li><li>在准备阶段以后，协调者和参与者执行的任务中都增加了超时，一旦超时，协调者和参与者都继续提交事务，默认为成功，这也是根据概率统计上超时后默认成功的正确性最大。</li></ul><blockquote><p>三阶段提交协议与两阶段提交协议相比，具有如上的优点，但是一旦发生超时，系统仍然会发生不一致，只不过这种情况很少见罢了，好处就是至少不会阻塞和永远锁定资源。</p></blockquote><h5 id="TTC-Try-Confirm-Cancel"><a href="#TTC-Try-Confirm-Cancel" class="headerlink" title="TTC(Try-Confirm-Cancel)"></a>TTC(Try-Confirm-Cancel)</h5><p>上面两节讲解了两阶段提交协议和三阶段提交协议，实际上他们能解决转账和下订单和扣库存中的分布式事务的问题，但是遇到极端情况，系统会发生阻塞或者不一致的问题，需要运营或者技术人工解决。无论两阶段还是三阶段方案中都包含多个参与者、多个阶段实现一个事务，实现复杂，性能也是一个很大的问题，因此，在互联网高并发系统中，鲜有使用两阶段提交和三阶段提交协议的场景。<br>阿里巴巴提出了新的TCC协议，TCC协议将一个任务拆分成Try、Confirm、Cancel，正常的流程会先执行Try，如果执行没有问题，再执行Confirm，如果执行过程中出了问题，则执行操作的逆操Cancel，从正常的流程上讲，这仍然是一个两阶段的提交协议，但是，在执行出现问题的时候，有一定的自我修复能力，如果任何一个参与者出现了问题，协调者通过执行操作的逆操作来取消之前的操作，达到最终的一致状态。<br>可以看出，从时序上，如果遇到极端情况下TCC会有很多问题的，例如，如果在Cancel的时候一些参与者收到指令，而一些参与者没有收到指令，整个系统仍然是不一致的，这种复杂的情况，系统首先会通过补偿的方式，尝试自动修复的，如果系统无法修复，必须由人工参与解决。<br>从TCC的逻辑上看，可以说TCC是简化版的三阶段提交协议，解决了两阶段提交协议的阻塞问题，但是没有解决极端情况下会出现不一致和脑裂的问题。然而，TCC通过自动化补偿手段，会把需要人工处理的不一致情况降到到最少，也是一种非常有用的解决方案，根据线人，阿里在内部的一些中间件上实现了TCC模式。<br>我们给出一个使用TCC的实际案例，在秒杀的场景，用户发起下单请求，应用层先查询库存，确认商品库存还有余量，则锁定库存，此时订单状态为待支付，然后指引用户去支付，由于某种原因用户支付失败，或者支付超时，系统会自动将锁定的库存解锁供其他用户秒杀。</p><h4 id="弱一致性"><a href="#弱一致性" class="headerlink" title="弱一致性"></a>弱一致性</h4><h5 id="最终一致性"><a href="#最终一致性" class="headerlink" title="最终一致性"></a>最终一致性</h5><p>最终一致性是弱一致性的一种特例，保证用户最终能够读取到某操作对系统特定数据的更新。但是随着时间的迁移，不同节点上的同一份数据总是在向趋同的方向变化。也可以简单的理解为在一段时间后，节点间的数据会最终达到一致状态。对于最终一致性最好的例子就是DNS系统，由于DNS多级缓存的实现，所以修改DNS记录后不会在全球所有DNS服务节点生效，需要等待DNS服务器缓存过期后向源服务器更新新的记录才能实现。最终一致性根据更新数据后各进程访问到数据的时间和方式的不同，又可以区分为：</p><ol><li>因果一致性：如果进程A通知进程B它已更新了一个数据项，那么进程B的后续访问将返回更新后的值，且一次写入将保证取代前一次写入。与进程A无因果关系的进程C的访问遵守一般的最终一致性规则。</li><li>读己之所写一致性：当进程A自己更新一个数据项之后，它总是访问到更新过的值，绝不会看到旧值。这是因果一致性模型的一个特例。</li><li>会话一致性：这是上一个模型的实用版本，它把访问存储系统的进程放到会话的上下文中。只要会话还存在，系统就保证“读己之所写”一致性。如果由于某些失败情形令会话终止，就要建立新的会话，而且系统的保证不会延续到新的会话。</li><li>单调读一致性：如果进程已经看到过数据对象的某个值，那么任何后续访问都不会返回在那个值之前的值。</li><li>单调写一致性：系统保证来自同一个进程的写操作顺序执行。要是系统不能保证这种程度的一致性，就非常难以编程了。</li></ol><p>同时最终一致性的不同方式可以进行组合，从服务端角度，如何尽快将更新后的数据分布到整个系统，降低达到最终一致性的时间窗口，是提高系统的可用度和用户体验非常重要的方面。对于分布式数据系统：</p><ul><li>N — 数据复制的份数</li><li>W — 更新数据是需要保证写完成的节点数</li><li>R — 读取数据的时候需要读取的节点数</li></ul><ol><li>如果<code>W+R&gt;N</code>：则是强一致性，写的节点和读的节点重叠。例如对于典型的一主一备同步复制的关系型数据库。<code>N=2,W=2,R=1</code>，则不管读的是主库还是备库的数据，都是一致的。</li><li>如果<code>W+R&lt;=N</code>：则是弱一致性。例如对于一主一备异步复制的关系型数据库，<code>N=2,W=1,R=1</code>，则如果读的是备库，就可能无法读取主库已经更新过的数据，所以是弱一致性。</li></ol><p>对于分布式系统，为了保证高可用性，一般设置N&gt;&#x3D;3。不同的N,W,R组合，是在可用性和一致性之间取一个平衡，以适应不同的应用场景。</p><ul><li>如果<code>N=W,R=1</code>，任何一个写节点失效，都会导致写失败，因此可用性会降低，但是由于数据分布的N个节点是同步写入的，因此可以保证强一致性。</li><li>如果<code>N=R,W=1</code>，只需要一个节点写入成功即可，写性能和可用性都比较高。但是读取其他节点的进程可能不能获取更新后的数据，因此是弱一致性。这种情况下，如果<code>W&lt;(N+1)/2</code>，并且写入的节点不重叠的话，则会存在写冲突。</li></ul><h2 id="负载均衡怎么做的呢，为什么这么做？"><a href="#负载均衡怎么做的呢，为什么这么做？" class="headerlink" title="负载均衡怎么做的呢，为什么这么做？"></a>负载均衡怎么做的呢，为什么这么做？</h2><h3 id="负载均衡相关知识"><a href="#负载均衡相关知识" class="headerlink" title="负载均衡相关知识"></a>负载均衡相关知识</h3><blockquote><p>软件负载解决的两个核心问题是：<strong>选谁</strong>、<strong>转发</strong>，其中最著名的是<code>LVS（Linux Virtual Server）</code>。</p></blockquote><h4 id="负载均衡分类"><a href="#负载均衡分类" class="headerlink" title="负载均衡分类"></a>负载均衡分类</h4><blockquote><p>最常用的是四层和七层负载均衡。</p></blockquote><h5 id="二层负载均衡"><a href="#二层负载均衡" class="headerlink" title="二层负载均衡"></a>二层负载均衡</h5><p>负载均衡服务器对外依然提供一个VIP（虚IP），集群中不同的机器采用相同IP地址，但机器的MAC地址不一样。当负载均衡服务器接受到请求之后，通过改写报文的目标MAC地址的方式将请求转发到目标机器实现负载均衡。</p><h5 id="三层负载均衡"><a href="#三层负载均衡" class="headerlink" title="三层负载均衡"></a>三层负载均衡</h5><p>和二层负载均衡类似，负载均衡服务器对外依然提供一个VIP（虚IP），但集群中不同的机器采用不同的IP地址。当负载均衡服务器接受到请求之后，根据不同的负载均衡算法，通过IP将请求转发至不同的真实服务器。</p><h5 id="四层负载均衡"><a href="#四层负载均衡" class="headerlink" title="四层负载均衡"></a>四层负载均衡</h5><p>四层负载均衡工作在OSI模型的传输层，由于在传输层，只有TCP&#x2F;UDP协议，这两种协议中除了包含源IP、目标IP以外，还包含源端口号及目的端口号。四层负载均衡服务器在接受到客户端请求后，以后通过修改数据包的地址信息（IP+端口号）将流量转发到应用服务器。</p><h5 id="七层负载均衡"><a href="#七层负载均衡" class="headerlink" title="七层负载均衡"></a>七层负载均衡</h5><p>七层负载均衡工作在OSI模型的应用层，应用层协议较多，常用http、radius、DNS等。七层负载就可以基于这些协议来负载。这些应用层协议中会包含很多有意义的内容。比如同一个Web服务器的负载均衡，除了根据IP加端口进行负载外，还可根据七层的URL、浏览器类别、语言来决定是否要进行负载均衡。</p><blockquote><p>对于一般的应用来说，有了Nginx就够了。Nginx可以用于七层负载均衡。但是对于一些大的网站，一般会采用DNS+四层负载+七层负载的方式进行多层次负载均衡。</p></blockquote><h4 id="常用负载均衡工具"><a href="#常用负载均衡工具" class="headerlink" title="常用负载均衡工具"></a>常用负载均衡工具</h4><blockquote><p>硬件负载均衡性能优越，功能全面，但价格昂贵，一般适合初期或者土豪级公司长期使用。因此软件负载均衡在互联网领域大量使用。常用的软件负载均衡软件有Nginx、LVS、HaProxy等。<br>Nginx&#x2F;LVS&#x2F;HAProxy是目前使用最广泛的三种负载均衡软件。</p></blockquote><h5 id="LVS"><a href="#LVS" class="headerlink" title="LVS"></a>LVS</h5><p>主要用来做四层负载均衡。</p><h5 id="Nginx"><a href="#Nginx" class="headerlink" title="Nginx"></a>Nginx</h5><p>是一个网页服务器，它能反向代理HTTP、HTTPS,、SMTP、POP3、IMAP的协议链接，以及一个负载均衡器和一个HTTP缓存。<br>Nginx主要用来做七层负载均衡。<br><strong>并发性能</strong>：官方支持每秒5万并发，实际国内一般到每秒2万并发，有优化到每秒10万并发的。具体性能看应用场景。</p><p>特点：</p><ul><li><strong>模块化设计</strong>：良好的扩展性，可以通过模块方式进行功能扩展。</li><li><strong>高可靠性</strong>：主控进程和worker是同步实现的，一个worker出现问题，会立刻启动另一个worker。</li><li><strong>内存消耗低</strong>：一万个长连接（keep-alive）,仅消耗2.5MB内存。</li><li><strong>支持热部署</strong>：不用停止服务器，实现更新配置文件，更换日志文件、更新服务器程序版本。</li><li><strong>并发能力强</strong>：官方数据每秒支持5万并发；</li><li><strong>功能丰富</strong>：优秀的反向代理功能和灵活的负载均衡策略</li></ul><h5 id="Haproxy"><a href="#Haproxy" class="headerlink" title="Haproxy"></a>Haproxy</h5><p>主要用来做七层负载均衡。</p><ul><li>静态负载均衡算法包括：轮询、比率、优先权。</li><li>动态负载均衡算法包括：最少连接数、最快响应速度、观察方法、预测法、动态性能分配、动态服务器补充、服务质量、服务类型、规则模式。</li></ul><h3 id="为什么这么做"><a href="#为什么这么做" class="headerlink" title="为什么这么做"></a>为什么这么做</h3><p>此部分主要注意一下几个问题：</p><ol><li>多节点包与配置的一致性</li><li>文件的存放（包含上传的文件与日志文件）</li><li>会话的共享与一致性（Session），如果是基于Restful的，虽然没有了Session但也需要注意某些信息的存储与验证，例如用户信息与缓存</li><li>如果是异构系统，需要注意通信报文格式的保证，比如使用消息队列，传输Json格式的数据。</li></ol><h2 id="了解过集群雪崩么？"><a href="#了解过集群雪崩么？" class="headerlink" title="了解过集群雪崩么？"></a>了解过集群雪崩么？</h2><blockquote><p>类似的问题还有<code>缓存的雪崩</code>，<code>缓存穿透</code>，<code>缓存击穿</code>等</p></blockquote><p><strong>雪崩效应产生的几种场景</strong></p><ul><li><strong>流量激增</strong>：比如异常流量、用户重试导致系统负载升高；</li><li><strong>缓存刷新</strong>：假设A为client端，B为Server端，假设A系统请求都流向B系统，请求超出了B系统的承载能力，就会造成B系统崩溃；</li><li><strong>程序有Bug</strong>：代码循环调用的逻辑问题，资源未释放引起的内存泄漏等问题；</li><li><strong>硬件故障</strong>：比如宕机，机房断电，光纤被挖断等。</li><li><strong>线程同步等待</strong>：系统间经常采用同步服务调用模式，核心服务和非核心服务共用一个线程池和消息队列。如果一个核心业务线程调用非核心线程，这个非核心线程交由第三方系统完成，当第三方系统本身出现问题，导致核心线程阻塞，一直处于等待状态，而进程间的调用是有超时限制的，最终这条线程将断掉，也可能引发雪崩；</li></ul><p><strong>常见解决方案</strong></p><blockquote><p>使用<code>超时策略</code>和<code>熔断器机制</code></p></blockquote><ul><li><strong>针对上述雪崩情景</strong>：有很多应对方案，但没有一个万能的模式能够应对所有场景。</li><li><strong>针对流量激增</strong>：采用自动扩缩容以应对突发流量，或在负载均衡器上安装限流模块。</li><li><strong>针对缓存刷新</strong>：参考Cache应用中的服务过载案例研究。</li><li><strong>针对硬件故障</strong>：多机房容灾，跨机房路由，异地多活等。</li><li><strong>针对同步等待</strong>：使用Hystrix做故障隔离，熔断器机制等可以解决依赖服务不可用的问题。</li></ul><h2 id="MySQL的主从复制怎么做的，具体原理是什么，有什么优缺点。"><a href="#MySQL的主从复制怎么做的，具体原理是什么，有什么优缺点。" class="headerlink" title="MySQL的主从复制怎么做的，具体原理是什么，有什么优缺点。"></a>MySQL的主从复制怎么做的，具体原理是什么，有什么优缺点。</h2><h3 id="基于Sql语句或行复制原理"><a href="#基于Sql语句或行复制原理" class="headerlink" title="基于Sql语句或行复制原理"></a>基于Sql语句或行复制原理</h3><ul><li>首先，mysql主库在事务提交时会把数据库变更作为事件Events记录在二进制文件binlog中；mysql主库上的sys_binlog控制binlog日志刷新到磁盘。</li><li>主库推送二进制文件binlog中的事件到从库的中继日志relay log,之后从库根据中继日志重做数据库变更操作。通过逻辑复制，以此来达到数据一致。</li></ul><blockquote><p>Mysql通过3个线程来完成主从库之间的数据复制：其中<code>BinLog Dump</code>线程跑在主库上，<code>I/O线程</code>和<code>SQL线程</code>跑在从库上。当从库启动复制（start slave）时，首先创建<code>I/O线程</code>连接主库，主库随后创建<code>Binlog Dump线程</code>读取数据库事件并发给<code>I/O线程</code>，<code>I/O线程</code>获取到数据库事件更新到从库的中继日志<code>Realy log</code>中去，之后从库上的SQL线程读取中继日志<code>relay log</code>中更新的数据库事件并应用。</p></blockquote><h3 id="基于GTID原理"><a href="#基于GTID原理" class="headerlink" title="基于GTID原理"></a>基于GTID原理</h3><ul><li>主节点更新数据时，会在事务前产生GTID，一起记录到binlog日志中。</li><li>从节点的I&#x2F;O线程将变更的bin log，写入到本地的relay log中。</li><li>SQL线程从relay log中获取GTID，然后对比本地binlog是否有记录（所以MySQL从节点必须要开启binary log）。</li><li>如果有记录，说明该GTID的事务已经执行，从节点会忽略。</li><li>如果没有记录，从节点就会从relay log中执行该GTID的事务，并记录到bin log。</li><li>在解析过程中会判断是否有主键，如果没有就用二级索引，如果有就用全部扫描。</li></ul><h3 id="优缺点"><a href="#优缺点" class="headerlink" title="优缺点"></a>优缺点</h3><ol><li>基于SQL语句的复制(statement-based replication, SBR)，</li></ol><ul><li>优点：<br>    历史悠久，技术成熟。<br>    产生的binlog文件较小，比较节省空间。<br>    binlog中包含了所有数据库更改信息，可以据此来审核数据库的安全等情况。<br>    binlog可以用于实时的还原，而不仅仅用于复制。<br>    主从版本可以不一样，从服务器版本可以比主服务器版本高。</li><li>缺点：<br>    不是所有的UPDATE语句都能被复制，尤其是包含不确定操作的时候。<br>    调用具有不确定因素的 UDF 时复制也可能出问题<br>    使用以下函数的语句也无法被复制：<ul><li>LOAD_FILE()</li><li>UUID()</li><li>USER()</li><li>FOUND_ROWS()</li><li>SYSDATE() (除非启动时启用了 –sysdate-is-now 选项)</li><li>INSERT … SELECT 会产生比 RBR 更多的行级锁</li></ul></li></ul><ol start="2"><li>基于行的复制(row-based replication, RBR)，</li></ol><ul><li>优点：<br>    任何情况都可以被复制，这对复制来说是最安全可靠的<br>    多数情况下，从服务器上的表如果有主键的话，复制就会快了很多<br>    复制以下几种语句时的行锁更少：<ul><li>INSERT … SELECT</li><li>包含 AUTO_INCREMENT 字段的 INSERT</li><li>没有附带条件或者并没有修改很多记录的 UPDATE 或 DELETE 语句<br>    - 执行 INSERT，UPDATE，DELETE 语句时锁更少<br>    - 从服务器上采用多线程来执行复制成为可能。</li></ul></li><li>缺点：<br>    binlog 文件太大<br>    复杂的回滚时 binlog 中会包含大量的数据<br>    主服务器上执行 UPDATE 语句时，所有发生变化的记录都会写到 binlog 中，而 SBR 只会写一次，这会导致频繁发生 binlog 的并发写问题<br>    UDF 产生的大 BLOB 值会导致复制变慢<br>    无法从 binlog 中看到都复制了写什么语句，无法进行审计。</li></ul><h2 id="Redis有哪些集群模式，各自的区别？"><a href="#Redis有哪些集群模式，各自的区别？" class="headerlink" title="Redis有哪些集群模式，各自的区别？"></a>Redis有哪些集群模式，各自的区别？</h2><ol><li>主从复制，基于<code>SYNC</code>命令，如果主节点都挂了，服务也就挂了，不能水平扩容</li><li>哨兵模式，基于状态监控，可选举出新的master节点，有一定弹性抗灾能力，不能水平扩容</li><li>Redis官方提供的Cluster集群模式(服务端)，基于数据分片</li><li>Jedis sharding集群(客户端sharding)，基于hash算法</li><li>利用中间件代理，比如codis等，具体看使用的中间件</li></ol><h2 id="项目用到了多线程，如果线程数很多会怎么样？"><a href="#项目用到了多线程，如果线程数很多会怎么样？" class="headerlink" title="项目用到了多线程，如果线程数很多会怎么样？"></a>项目用到了多线程，如果线程数很多会怎么样？</h2><blockquote><p>这个问题属于一个比较开发性的问题，且能延伸出很多东西。<br>首先线程数过多，会导致CPU占用率居高不下，相应的散热需求就会上升，费电是一方面，如果占用率长时间居高不下很有可能导致服务器宕机。</p></blockquote><blockquote><p>Q: 那么如何控制线程数？<br>A: 线程池。消息队列。</p></blockquote><blockquote><p>Q: 如何确定一个合理的线程数？<br>A: 处理器敏感的程序，和CPU核心数持平；对于处理并发和网络通讯的程序，开几个到几十个，但不宜太多。</p></blockquote><blockquote><p>Q: 如何处理大任务？<br>A: 使用Fork&#x2F;Join 框。</p></blockquote><h2 id="分布式了解哪些东西，消息队列了解么，用在什么场景？怎么保证Kafka数据不丢失，以及确保消息不会被重复消费？消息送达确认是怎么做的？"><a href="#分布式了解哪些东西，消息队列了解么，用在什么场景？怎么保证Kafka数据不丢失，以及确保消息不会被重复消费？消息送达确认是怎么做的？" class="headerlink" title="分布式了解哪些东西，消息队列了解么，用在什么场景？怎么保证Kafka数据不丢失，以及确保消息不会被重复消费？消息送达确认是怎么做的？"></a>分布式了解哪些东西，消息队列了解么，用在什么场景？怎么保证Kafka数据不丢失，以及确保消息不会被重复消费？消息送达确认是怎么做的？</h2><blockquote><p>应用场景：削峰，限流和异步</p></blockquote><blockquote><p>消息队列 Kafka</p></blockquote><p>关于kafka<br>使用同步模式的时候，有3种状态保证消息被安全生产，在配置为1（只保证写入leader成功）的话，如果刚好leader partition挂了，数据就会丢失。<br>还有一种情况可能会丢失消息，就是使用异步模式的时候，当缓冲区满了，如果配置为0（还没有收到确认的情况下，缓冲池一满，就清空缓冲池里的消息），<br>数据就会被立即丢弃掉。</p><p>在数据生产时避免数据丢失的方法：<br>只要能避免上述两种情况，那么就可以保证消息不会被丢失。<br>就是说在同步模式的时候，确认机制设置为-1，也就是让消息写入leader和所有的副本。<br>还有，在异步模式下，如果消息发出去了，但还没有收到确认的时候，缓冲池满了，在配置文件中设置成不限制阻塞超时的时间，也就说让生产端一直阻塞，这样也能保证数据不会丢失。</p><p>在数据消费时，避免数据丢失的方法：如果使用了storm，要开启storm的ackfail机制；如果没有使用storm，确认数据被完成处理之后，再更新offset值。低级API中需要手动控制offset值。</p><p>数据重复消费的情况，如果处理？</p><ul><li>去重：将消息的唯一标识保存到外部介质中，每次消费处理时判断是否处理过；</li><li>不管：大数据场景中，报表系统或者日志信息丢失几条都无所谓，不会影响最终的统计分析结果。</li></ul><p>Kafka本身支持At least once消息送达语义，因此实现消息发送的幂等关键是要实现Broker端消息的去重。为了实现消息发送的幂等性，Kafka引入了两个新的概念：</p><ul><li><strong>PID</strong>:每个新的Producer在初始化的时候会被分配一个唯一的PID，这个PID对用户是不可见的。</li><li><strong>Sequence Numbler</strong>:对于每个PID，该Producer发送数据的每个<code>&lt;Topic, Partition&gt;</code>都对应一个从0开始单调递增的Sequence Number</li></ul><blockquote><p>Broker端在缓存中保存了这Sequence Numbler，对于接收的每条消息，如果其序号比Broker缓存中序号大于1则接受它，否则将其丢弃。这样就可以实现了消息重复提交了。但是，只能保证单个Producer对于同一个&lt;Topic, Partition&gt;的Exactly Once语义。不能保证同一个Producer一个topic不同的partion幂等。</p></blockquote><h1 id="四面-HR面"><a href="#四面-HR面" class="headerlink" title="四面(HR面)"></a>四面(HR面)</h1><blockquote><p>这里全靠自己发挥了</p></blockquote><ol><li>工作中遇到的最大挑战是什么，你如何克服的？</li><li>你最大的优点和最大的缺点，各自说一个？<blockquote><p>缺点尽量说一些无关痛痒的，不要说自己不善于表达。</p></blockquote></li><li>未来的职业发展，短期和长期的规划是什么？<blockquote><p>实在迷茫没想法，想一想自己是不是有心力去钻研技术，有就说技术晋升路线，没有就管理晋升路线，百度或Google上有很多模板可以借鉴。</p></blockquote></li></ol>]]></content>
    
    
    <summary type="html">Java面试真题-阿里P6面试</summary>
    
    
    
    <category term="Java" scheme="https://newgr8player.top/categories/Java/"/>
    
    
    <category term="Java" scheme="https://newgr8player.top/tags/Java/"/>
    
    <category term="面试" scheme="https://newgr8player.top/tags/%E9%9D%A2%E8%AF%95/"/>
    
  </entry>
  
  <entry>
    <title>Java面试</title>
    <link href="https://newgr8player.top/posts/Java%E9%9D%A2%E8%AF%95/"/>
    <id>https://newgr8player.top/posts/Java%E9%9D%A2%E8%AF%95/</id>
    <published>2019-08-27T01:05:15.000Z</published>
    <updated>2019-09-05T02:10:50.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="基础与常识"><a href="#基础与常识" class="headerlink" title="基础与常识"></a>基础与常识</h1><h2 id="面向对象和面向过程的区别"><a href="#面向对象和面向过程的区别" class="headerlink" title="面向对象和面向过程的区别"></a>面向对象和面向过程的区别</h2><ul><li><strong>面向过程</strong> ：<strong>面向过程性能比面向对象高。</strong> 因为类调用时需要实例化，开销比较大，比较消耗资源，所以当性能是最重要的考量因素的时候，比如单片机、嵌入式开发、Linux&#x2F;Unix等一般采用面向过程开发。但是，<strong>面向过程没有面向对象易维护、易复用、易扩展。</strong> </li><li><strong>面向对象</strong> ：<strong>面向对象易维护、易复用、易扩展。</strong> 因为面向对象有封装、继承、多态性的特性，所以可以设计出低耦合的系统，使系统更加灵活、更加易于维护。但是，<strong>面向对象性能比面向过程低</strong>。</li></ul><blockquote><p><strong>Q:面向过程性能比面向对象高</strong><br><strong>A:</strong> 面向过程也需要分配内存，计算内存偏移量，Java性能差的主要原因并不是因为它是面向对象语言，而是Java是半编译语言，最终的执行代码并不是可以直接被CPU执行的二进制机械码。<br>而面向过程语言大多都是直接编译成机械码在电脑上执行，并且其它一些面向过程的脚本语言性能也并不一定比Java好。</p></blockquote><h2 id="关于JVM、JDK和JRE最详细通俗的解答"><a href="#关于JVM、JDK和JRE最详细通俗的解答" class="headerlink" title="关于JVM、JDK和JRE最详细通俗的解答"></a>关于<code>JVM</code>、<code>JDK</code>和<code>JRE</code>最详细通俗的解答</h2><p><strong>JVM</strong></p><p>Java虚拟机（JVM）是运行 Java 字节码的虚拟机。JVM有针对不同系统的特定实现（Windows，Linux，macOS），目的是使用相同的字节码，它们都会给出相同的结果。</p><p><strong>什么是字节码?采用字节码的好处是什么?</strong></p><blockquote><p>在 Java 中，JVM可以理解的代码就叫做<code>字节码</code>（即扩展名为 <code>.class</code> 的文件），它不面向任何特定的处理器，只面向虚拟机。Java 语言通过字节码的方式，在一定程度上解决了传统解释型语言执行效率低的问题，同时又保留了解释型语言可移植的特点。所以 Java 程序运行时比较高效，而且，由于字节码并不针对一种特定的机器，因此，Java程序无须重新编译便可在多种不同操作系统的计算机上运行。</p></blockquote><p><strong>Java 程序从源代码到运行一般有下面3步：</strong><br><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/27/cfba71c0-c867-11e9-afb6-c9e9c0891586.png" alt="Java 程序运行过程.png"></p><p>我们需要格外注意的是 .class-&gt;机器码 这一步。在这一步 JVM 类加载器首先加载字节码文件，然后通过解释器逐行解释执行，这种方式的执行速度会相对比较慢。而且，有些方法和代码块是经常需要被调用的(也就是所谓的热点代码)，所以后面引进了 JIT 编译器，而JIT 属于运行时编译。当 JIT 编译器完成第一次编译后，其会将字节码对应的机器码保存下来，下次可以直接使用。而我们知道，机器码的运行效率肯定是高于 Java 解释器的。这也解释了我们为什么经常会说 Java 是编译与解释共存的语言。</p><blockquote><p>HotSpot采用了惰性评估(Lazy Evaluation)的做法，根据二八定律，消耗大部分系统资源的只有那一小部分的代码（热点代码），而这也就是JIT所需要编译的部分。JVM会根据代码每次被执行的情况收集信息并相应地做出一些优化，因此执行的次数越多，它的速度就越快。JDK 9引入了一种新的编译模式AOT(Ahead of Time Compilation)，它是直接将字节码编译成机器码，这样就避免了JIT预热等各方面的开销。JDK支持分层编译和AOT协作使用。但是 ，AOT 编译器的编译质量是肯定比不上 JIT 编译器的。</p></blockquote><p><strong>JDK</strong></p><p>JDK是Java Development Kit，它是功能齐全的Java SDK。它拥有JRE所拥有的一切，还有编译器（javac）和工具（如javadoc和jdb）。它能够创建和编译程序。</p><p><strong>JRE</strong></p><p>JRE 是 Java运行时环境。它是运行已编译 Java 程序所需的所有内容的集合，包括 Java虚拟机（JVM），Java类库，java命令和其他的一些基础构件。但是，它不能用于创建新程序。</p><blockquote><p>如果你只是为了运行一下 Java 程序的话，那么你只需要安装 JRE 就可以了。如果你需要进行一些 Java 编程方面的工作，那么你就需要安装JDK了。但是，这不是绝对的。有时，即使您不打算在计算机上进行任何Java开发，仍然需要安装JDK。例如，如果要使用JSP部署Web应用程序，那么从技术上讲，您只是在应用程序服务器中运行Java程序。那你为什么需要JDK呢？因为应用程序服务器会将 JSP 转换为 Java servlet，并且需要使用 JDK 来编译 servlet。</p></blockquote><h2 id="Oracle-JDK-和-OpenJDK-的对比"><a href="#Oracle-JDK-和-OpenJDK-的对比" class="headerlink" title="Oracle JDK 和 OpenJDK 的对比"></a><code>Oracle JDK</code> 和 <code>OpenJDK</code> 的对比</h2><ol><li>Oracle JDK版本将每三年发布一次，而OpenJDK版本每三个月发布一次；</li><li>OpenJDK 是一个参考模型并且是完全开源的，而Oracle JDK是OpenJDK的一个实现，并不是完全开源的；</li><li>Oracle JDK 比 OpenJDK 更稳定。OpenJDK和Oracle JDK的代码几乎相同，但Oracle JDK有更多的类和一些错误修复。因此，如果您想开发企业&#x2F;商业软件，我建议您选择Oracle JDK，因为它经过了彻底的测试和稳定。某些情况下，有些人提到在使用OpenJDK 可能会遇到了许多应用程序崩溃的问题，但是，只需切换到Oracle JDK就可以解决问题；</li><li>在响应性和JVM性能方面，Oracle JDK与OpenJDK相比提供了更好的性能；</li><li>Oracle JDK不会为即将发布的版本提供长期支持，用户每次都必须通过更新到最新版本获得支持来获取最新版本；</li><li>Oracle JDK根据二进制代码许可协议获得许可，而OpenJDK根据GPL v2许可获得许可。</li></ol><h2 id="字符型常量和字符串常量的区别？"><a href="#字符型常量和字符串常量的区别？" class="headerlink" title="字符型常量和字符串常量的区别？"></a>字符型常量和字符串常量的区别？</h2><ol><li>形式上: 字符常量是单引号引起的一个字符; 字符串常量是双引号引起的若干个字符</li><li>含义上: 字符常量相当于一个整型值( ASCII 值),可以参加表达式运算; 字符串常量代表一个地址值(该字符串在内存中存放位置)</li><li>占内存大小 字符常量只占2个字节; 字符串常量占若干个字节(至少一个字符结束标志) (<strong>注意： char在Java中占两个字节</strong>)</li></ol><blockquote><p>java编程思想第四版：2.2.2节<br><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/27/ad8e82b0-c869-11e9-afb6-c9e9c0891586.jpg" alt="java编程思想第四版：2.2.2节.jpg"></p></blockquote><h2 id="构造器-Constructor-是否可被-override"><a href="#构造器-Constructor-是否可被-override" class="headerlink" title="构造器 Constructor 是否可被 override"></a>构造器 Constructor 是否可被 override</h2><p>在讲继承的时候我们就知道父类的私有属性和构造方法并不能被继承，所以 Constructor 也就不能被 override（重写）,但是可以 overload（重载）,所以你可以看到一个类中有多个构造函数的情况。</p><h2 id="重载和重写的区别"><a href="#重载和重写的区别" class="headerlink" title="重载和重写的区别"></a>重载和重写的区别</h2><ul><li><strong>重载：</strong> 发生在同一个类中，方法名必须相同，参数类型不同、个数不同、顺序不同，方法返回值和访问修饰符可以不同，发生在编译时。 　　</li><li><strong>重写：</strong>   发生在父子类中，方法名、参数列表必须相同，返回值范围小于等于父类，抛出的异常范围小于等于父类，访问修饰符范围大于等于父类；如果父类方法访问修饰符为 private 则子类就不能重写该方法。</li></ul><h2 id="Java-面向对象编程三大特性-封装、继承、多态"><a href="#Java-面向对象编程三大特性-封装、继承、多态" class="headerlink" title="Java 面向对象编程三大特性: 封装、继承、多态"></a>Java 面向对象编程三大特性: 封装、继承、多态</h2><h3 id="封装"><a href="#封装" class="headerlink" title="封装"></a>封装</h3><p>封装把一个对象的属性私有化，同时提供一些可以被外界访问的属性的方法，如果属性不想被外界访问，我们大可不必提供方法给外界访问。但是如果一个类没有提供给外界访问的方法，那么这个类也没有什么意义了。</p><h3 id="继承"><a href="#继承" class="headerlink" title="继承"></a>继承</h3><p>继承是使用已存在的类的定义作为基础建立新类的技术，新类的定义可以增加新的数据或新的功能，也可以用父类的功能，但不能选择性地继承父类。通过使用继承我们能够非常方便地复用以前的代码。</p><p><strong>关于继承如下 3 点请记住：</strong></p><ol><li>子类拥有父类对象所有的属性和方法（包括私有属性和私有方法），但是父类中的私有属性和方法子类是无法访问，<strong>只是拥有</strong>。</li><li>子类可以拥有自己属性和方法，即子类可以对父类进行扩展。</li><li>子类可以用自己的方式实现父类的方法。（以后介绍）。</li></ol><h3 id="多态"><a href="#多态" class="headerlink" title="多态"></a>多态</h3><p>所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定，而是在程序运行期间才确定，即一个引用变量到底会指向哪个类的实例对象，该引用变量发出的方法调用到底是哪个类中实现的方法，必须在由程序运行期间才能决定。</p><p>在Java中有两种形式可以实现多态：继承（多个子类对同一方法的重写）和接口（实现接口并覆盖接口中同一方法）。</p><h2 id="String、StringBuffer-和-StringBuilder-的区别是什么-String-为什么是不可变的"><a href="#String、StringBuffer-和-StringBuilder-的区别是什么-String-为什么是不可变的" class="headerlink" title="String、StringBuffer 和 StringBuilder 的区别是什么? String 为什么是不可变的?"></a><code>String</code>、<code>StringBuffer</code> 和 <code>StringBuilder</code> 的区别是什么? <code>String</code> 为什么是不可变的?</h2><p><strong>可变性</strong></p><p>简单的来说：String 类中使用 final 关键字修饰字符数组来保存字符串，<code>private　final　char　value[]</code>，所以 String 对象是不可变的。而StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类，在 AbstractStringBuilder 中也是使用字符数组保存字符串<code>char[]value</code> 但是没有用 final 关键字修饰，所以这两种对象都是可变的。</p><p>StringBuilder 与 StringBuffer 的构造方法都是调用父类构造方法也就是 AbstractStringBuilder 实现的，大家可以自行查阅源码。</p><p><em><strong>AbstractStringBuilder.java</strong></em></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title class_">AbstractStringBuilder</span> <span class="keyword">implements</span> <span class="title class_">Appendable</span>, CharSequence &#123;</span><br><span class="line">    <span class="type">char</span>[] value;</span><br><span class="line">    <span class="type">int</span> count;</span><br><span class="line">    AbstractStringBuilder() &#123;</span><br><span class="line">    &#125;</span><br><span class="line">    AbstractStringBuilder(<span class="type">int</span> capacity) &#123;</span><br><span class="line">        value = <span class="keyword">new</span> <span class="title class_">char</span>[capacity];</span><br><span class="line">    &#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><strong>线程安全性</strong></p><p>String 中的对象是不可变的，也就可以理解为常量，线程安全。AbstractStringBuilder 是 StringBuilder 与 StringBuffer 的公共父类，定义了一些字符串的基本操作，如 expandCapacity、append、insert、indexOf 等公共方法。StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁，所以是线程安全的。StringBuilder 并没有对方法进行加同步锁，所以是非线程安全的。　</p><p><strong>性能</strong></p><p>每次对 String 类型进行改变的时候，都会生成一个新的 String 对象，然后将指针指向新的 String 对象。StringBuffer 每次都会对 StringBuffer 对象本身进行操作，而不是生成新的对象并改变对象引用。相同情况下使用 StringBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升，但却要冒多线程不安全的风险。</p><p><strong>对于三者使用的总结：</strong> </p><ol><li>操作少量的数据: 适用String</li><li>单线程操作字符串缓冲区下操作大量数据: 适用StringBuilder</li><li>多线程操作字符串缓冲区下操作大量数据: 适用StringBuffer</li></ol><h2 id="自动装箱与拆箱"><a href="#自动装箱与拆箱" class="headerlink" title="自动装箱与拆箱"></a>自动装箱与拆箱</h2><ul><li><strong>装箱</strong>：将基本类型用它们对应的引用类型包装起来；</li><li><strong>拆箱</strong>：将包装类型转换为基本数据类型；</li></ul><h2 id="在一个静态方法内调用一个非静态成员为什么是非法的"><a href="#在一个静态方法内调用一个非静态成员为什么是非法的" class="headerlink" title="在一个静态方法内调用一个非静态成员为什么是非法的?"></a>在一个静态方法内调用一个非静态成员为什么是非法的?</h2><p>由于静态方法可以不通过对象进行调用，因此在静态方法里，不能调用其他非静态变量，也不可以访问非静态变量成员。</p><h2 id="接口和抽象类的区别是什么？"><a href="#接口和抽象类的区别是什么？" class="headerlink" title="接口和抽象类的区别是什么？"></a>接口和抽象类的区别是什么？</h2><ol><li>接口的方法默认是 public，所有方法在接口中不能有实现(Java 8 开始接口方法可以有默认实现），而抽象类可以有非抽象的方法。</li><li>接口中除了static、final变量，不能有其他变量，而抽象类中则不一定。</li><li>一个类可以实现多个接口，但只能实现一个抽象类。接口自己本身可以通过extends关键字扩展多个接口。</li><li>接口方法默认修饰符是public，抽象方法可以有public、protected和default这些修饰符（抽象方法就是为了被重写所以不能使用private关键字修饰！）。 </li><li>从设计层面来说，抽象是对类的抽象，是一种模板设计，而接口是对行为的抽象，是一种行为的规范。</li></ol><blockquote><p>在JDK8中，接口也可以定义静态方法，可以直接用接口名调用。实现类和实现是不可以调用的。如果同时实现两个接口，接口中定义了一样的默认方法，则必须重写，不然会报错。</p><ul><li>关于抽象类<ol><li>JDK 1.8以前，抽象类的方法默认访问权限为protected</li><li>JDK 1.8时，抽象类的方法默认访问权限变为default</li></ol></li><li>关于接口<ol><li>JDK 1.8以前，接口中的方法必须是public的</li><li>JDK 1.8时，接口中的方法可以是public的，也可以是default的</li><li>JDK 1.9时，接口中的方法可以是private的</li></ol></li></ul></blockquote><h2 id="成员变量与局部变量的区别有哪些？"><a href="#成员变量与局部变量的区别有哪些？" class="headerlink" title="成员变量与局部变量的区别有哪些？"></a>成员变量与局部变量的区别有哪些？</h2><ol><li>从语法形式上看:成员变量是属于类的，而局部变量是在方法中定义的变量或是方法的参数；成员变量可以被 public,private,static 等修饰符所修饰，而局部变量不能被访问控制修饰符及 static 所修饰；但是，成员变量和局部变量都能被 final 所修饰。</li><li>从变量在内存中的存储方式来看:如果成员变量是使用<code>static</code>修饰的，那么这个成员变量是属于类的，如果没有使用<code>static</code>修饰，这个成员变量是属于实例的。而对象存在于堆内存，局部变量则存在于栈内存。</li><li>从变量在内存中的生存时间上看:成员变量是对象的一部分，它随着对象的创建而存在，而局部变量随着方法的调用而自动消失。</li><li>成员变量如果没有被赋初值:则会自动以类型的默认值而赋值（一种情况例外:被 final 修饰的成员变量也必须显式地赋值），而局部变量则不会自动赋值。</li></ol><h2 id="创建一个对象用什么运算符-对象实体与对象引用有何不同"><a href="#创建一个对象用什么运算符-对象实体与对象引用有何不同" class="headerlink" title="创建一个对象用什么运算符?对象实体与对象引用有何不同?"></a>创建一个对象用什么运算符?对象实体与对象引用有何不同?</h2><p><code>new</code>运算符，<code>new</code>创建对象实例（对象实例在堆内存中），对象引用指向对象实例（对象引用存放在栈内存中）。一个对象引用可以指向0个或1个对象（一根绳子可以不系气球，也可以系一个气球）;一个对象可以有n个引用指向它（可以用n条绳子系住一个气球）。</p><h2 id="构造方法有哪些特性？"><a href="#构造方法有哪些特性？" class="headerlink" title="构造方法有哪些特性？"></a>构造方法有哪些特性？</h2><ol><li>名字与类名相同。</li><li>没有返回值，但不能用void声明构造函数。</li><li>生成类的对象时自动执行，无需调用。</li></ol><h2 id="静态方法和实例方法有何不同"><a href="#静态方法和实例方法有何不同" class="headerlink" title="静态方法和实例方法有何不同"></a>静态方法和实例方法有何不同</h2><ol><li><p>在外部调用静态方法时，可以使用”类名.方法名”的方式，也可以使用”对象名.方法名”的方式。而实例方法只有后面这种方式。也就是说，调用静态方法可以无需创建对象。 </p></li><li><p>静态方法在访问本类的成员时，只允许访问静态成员（即静态成员变量和静态方法），而不允许访问实例成员变量和实例方法；实例方法则无此限制。</p></li></ol><h2 id="对象的相等与指向他们的引用相等-两者有什么不同"><a href="#对象的相等与指向他们的引用相等-两者有什么不同" class="headerlink" title="对象的相等与指向他们的引用相等,两者有什么不同?"></a>对象的相等与指向他们的引用相等,两者有什么不同?</h2><p>对象的相等，比的是内存中存放的内容是否相等。而引用相等，比较的是他们指向的内存地址是否相等。</p><blockquote><p>对象的相等一般指的是调用<code>equals()</code>方法进行比较<br>引用相等一般指<code>==</code>判等操作符进行比较</p></blockquote><h2 id="与-equals-重要"><a href="#与-equals-重要" class="headerlink" title="&#x3D;&#x3D; 与 equals(重要)"></a>&#x3D;&#x3D; 与 equals(重要)</h2><p><strong>&#x3D;&#x3D;</strong> : 它的作用是判断两个对象的地址是不是相等。即，判断两个对象是不是同一个对象(基本数据类型&#x3D;&#x3D;比较的是值，引用数据类型&#x3D;&#x3D;比较的是内存地址)。</p><p><strong>equals()</strong> : 它的作用也是判断两个对象是否相等。但它一般有两种使用情况：</p><ul><li>情况1：类没有覆盖 equals() 方法。则通过 equals() 比较该类的两个对象时，等价于通过“&#x3D;&#x3D;”比较这两个对象。</li><li>情况2：类覆盖了 equals() 方法。一般，我们都覆盖 equals() 方法来比较两个对象的内容是否相等；若它们的内容相等，则返回 true (即，认为这两个对象相等)。</li></ul><p><strong>举个例子：</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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="keyword">public</span> <span class="keyword">class</span> <span class="title class_">test1</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">a</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">String</span>(<span class="string">&quot;ab&quot;</span>); <span class="comment">// a 为一个引用</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">b</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">String</span>(<span class="string">&quot;ab&quot;</span>); <span class="comment">// b为另一个引用,对象的内容一样</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">aa</span> <span class="operator">=</span> <span class="string">&quot;ab&quot;</span>; <span class="comment">// 放在常量池中</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">bb</span> <span class="operator">=</span> <span class="string">&quot;ab&quot;</span>; <span class="comment">// 从常量池中查找</span></span><br><span class="line">        <span class="keyword">if</span> (aa == bb) <span class="comment">// true</span></span><br><span class="line">            System.out.println(<span class="string">&quot;aa==bb&quot;</span>);</span><br><span class="line">        <span class="keyword">if</span> (a == b) <span class="comment">// false，非同一对象</span></span><br><span class="line">            System.out.println(<span class="string">&quot;a==b&quot;</span>);</span><br><span class="line">        <span class="keyword">if</span> (a.equals(b)) <span class="comment">// true</span></span><br><span class="line">            System.out.println(<span class="string">&quot;aEQb&quot;</span>);</span><br><span class="line">        <span class="keyword">if</span> (<span class="number">42</span> == <span class="number">42.0</span>) &#123; <span class="comment">// true</span></span><br><span class="line">            System.out.println(<span class="string">&quot;true&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"></span><br></pre></td></tr></table></figure><p><strong>说明：</strong></p><ul><li>String 中的 equals 方法是被重写过的，因为 object 的 equals 方法是比较的对象的内存地址，而 String 的 equals 方法比较的是对象的值。</li><li>当创建 String 类型的对象时，虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象，如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个 String 对象。</li></ul><h2 id="hashCode-与-equals-重要"><a href="#hashCode-与-equals-重要" class="headerlink" title="hashCode 与 equals (重要)"></a>hashCode 与 equals (重要)</h2><blockquote><p>面试官可能会问你：”你重写过 hashcode 和 equals 么，为什么重写equals时必须重写hashCode方法？”</p></blockquote><h3 id="hashCode（）介绍"><a href="#hashCode（）介绍" class="headerlink" title="hashCode（）介绍"></a>hashCode（）介绍</h3><p>hashCode() 的作用是获取哈希码，也称为散列码；它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在JDK的Object.java中，这就意味着Java中的任何类都包含有hashCode() 函数。</p><p>散列表存储的是键值对(key-value)，它的特点是：能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码！（可以快速找到所需要的对象）</p><h3 id="为什么要有-hashCode"><a href="#为什么要有-hashCode" class="headerlink" title="为什么要有 hashCode"></a>为什么要有 hashCode</h3><p><strong>我们先以”HashSet 如何检查重复”为例子来说明为什么要有 hashCode：</strong> 当你把对象加入 HashSet 时，HashSet 会先计算对象的 hashcode 值来判断对象加入的位置，同时也会与其他已经加入的对象的 hashcode 值作比较，如果没有相符的hashcode，HashSet会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象，这时会调用 <code>equals()</code>方法来检查 hashcode 相等的对象是否真的相同。如果两者相同，HashSet 就不会让其加入操作成功。如果不同的话，就会重新散列到其他位置。（摘自我的Java启蒙书《Head first java》第二版）。这样我们就大大减少了 equals 的次数，相应就大大提高了执行速度。</p><p>通过我们可以看出：<code>hashCode()</code> 的作用就是<strong>获取哈希码</strong>，也称为散列码；它实际上是返回一个int整数。这个<strong>哈希码的作用</strong>是确定该对象在哈希表中的索引位置。**<code>hashCode() </code>在散列表中才有用，在其它情况下没用。**在散列表中hashCode() 的作用是获取对象的散列码，进而确定该对象在散列表中的位置。</p><h3 id="hashCode-与equals-的相关规定"><a href="#hashCode-与equals-的相关规定" class="headerlink" title="hashCode()与equals()的相关规定"></a><code>hashCode()</code>与<code>equals()</code>的相关规定</h3><ol><li>如果两个对象相等，则hashcode一定也是相同的</li><li>两个对象相等,对两个对象分别调用equals方法都返回true</li><li>两个对象有相同的hashcode值，它们也不一定是相等的</li><li><strong>因此，equals 方法被覆盖过，则 hashCode 方法也必须被覆盖</strong></li><li>hashCode() 的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode()，则该 class 的两个对象无论如何都不会相等（即使这两个对象指向相同的数据）</li></ol><h2 id="简述程序、进程、线程的基本概念。以及他们之间关系是什么"><a href="#简述程序、进程、线程的基本概念。以及他们之间关系是什么" class="headerlink" title="简述程序、进程、线程的基本概念。以及他们之间关系是什么?"></a>简述<code>程序</code>、<code>进程</code>、<code>线程</code>的基本概念。以及他们之间关系是什么?</h2><p><strong>程序</strong>是含有指令和数据的文件，被存储在磁盘或其他的数据存储设备中，也就是说程序是静态的代码。</p><p><strong>进程</strong>是程序的一次执行过程，是系统运行程序的基本单位，因此进程是动态的。系统运行一个程序即是一个进程从创建，运行到消亡的过程。简单来说，一个进程就是一个执行中的程序，它在计算机中一个指令接着一个指令地执行着，同时，每个进程还占有某些系统资源如CPU时间，内存空间，文件，输入输出设备的使用权等等。换句话说，当程序在执行时，将会被操作系统载入内存中。</p><p><strong>线程</strong>与进程相似，但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源，所以系统在产生一个线程，或是在各个线程之间作切换工作时，负担要比进程小得多，也正因为如此，线程也被称为轻量级进程。 </p><blockquote><p>关系：线程是进程划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的，而各线程则不一定，因为同一进程中的线程极有可能会相互影响。从另一角度来说，进程属于操作系统的范畴，主要是同一段时间内，可以同时执行一个以上的程序，而线程则是在同一程序内几乎同时执行一个以上的程序段。</p></blockquote><h2 id="线程有哪些基本状态？"><a href="#线程有哪些基本状态？" class="headerlink" title="线程有哪些基本状态？"></a>线程有哪些基本状态？</h2><p>Java 线程在运行的生命周期中的指定时刻只可能处于下面6种不同状态的其中一个状态（图源《Java 并发编程艺术》4.1.4节）:<br><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/27/becd4ea0-c86c-11e9-afb6-c9e9c0891586.png" alt="Java线程的状态.png"></p><p>线程在生命周期中并不是固定处于某一个状态而是随着代码的执行在不同状态之间切换。Java 线程状态变迁如下图所示（图源《Java 并发编程艺术》4.1.4节）：<br><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/27/e5987d70-c86c-11e9-afb6-c9e9c0891586.png" alt="Java 线程状态变迁.png"></p><p>由上图可以看出：</p><p>线程创建之后它将处于 <strong>NEW（新建）</strong> 状态，调用 <code>start()</code> 方法后开始运行，线程这时候处于 <strong>READY（可运行）</strong> 状态。可运行状态的线程获得了 cpu 时间片（timeslice）后就处于 <strong>RUNNING（运行）</strong> 状态。</p><blockquote><p>操作系统隐藏 Java虚拟机（JVM）中的 RUNNABLE 和 RUNNING 状态，它只能看到 RUNNABLE 状态（图源：<a href="https://howtodoinjava.com/">HowToDoInJava</a>：<a href="https://howtodoinjava.com/java/multi-threading/java-thread-life-cycle-and-thread-states/">Java Thread Life Cycle and Thread States</a>），所以 Java 系统一般将这两个状态统称为 <strong>RUNNABLE（运行中）</strong> 状态 。<br><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/27/09279410-c86d-11e9-afb6-c9e9c0891586.png" alt="RUNNABLEVSRUNNING.png"></p></blockquote><p>当线程执行 <code>wait()</code>方法之后，线程进入 <strong>WAITING（等待）</strong> 状态。进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态，而 <strong>TIME_WAITING(超时等待)</strong> 状态相当于在等待状态的基础上增加了超时限制，比如通过 <code>sleep（long millis）</code>方法或 <code>wait（long millis）</code>方法可以将 Java 线程置于 TIMED WAITING 状态。当超时时间到达后 Java 线程将会返回到 RUNNABLE 状态。当线程调用同步方法时，在没有获取到锁的情况下，线程将会进入到 <strong>BLOCKED（阻塞）</strong> 状态。线程在执行 Runnable 的<code>run()</code>方法之后将会进入到 <strong>TERMINATED（终止）</strong> 状态。</p><h2 id="关于-final-关键字的一些总结"><a href="#关于-final-关键字的一些总结" class="headerlink" title="关于 final 关键字的一些总结"></a>关于 final 关键字的一些总结</h2><blockquote><p>final关键字主要用在三个地方：变量、方法、类。</p></blockquote><ol><li>对于一个final变量，如果是基本数据类型的变量，则其数值一旦在初始化之后便不能更改；如果是引用类型的变量，则在对其初始化之后便不能再让其指向另一个对象。</li><li>当用final修饰一个类时，表明这个类不能被继承。final类中的所有成员方法都会被隐式地指定为final方法。</li><li>使用final方法的原因有两个。第一个原因是把方法锁定，以防任何继承类修改它的含义；第二个原因是效率。在早期的Java实现版本中，会将final方法转为内嵌调用。但是如果方法过于庞大，可能看不到内嵌调用带来的任何性能提升（现在的Java版本已经不需要使用final方法进行这些优化了）。类中所有的private方法都隐式地指定为final。</li></ol><h2 id="Java-中的异常处理"><a href="#Java-中的异常处理" class="headerlink" title="Java 中的异常处理"></a>Java 中的异常处理</h2><p><strong>Java异常类层次结构图</strong><br><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/27/4c862140-c86d-11e9-afb6-c9e9c0891586.png" alt="Exception.png"></p><p>在 Java 中，所有的异常都有一个共同的祖先java.lang包中的 <strong>Throwable类</strong>。Throwable： 有两个重要的子类：<strong>Exception（异常）</strong> 和 <strong>Error（错误）</strong> ，二者都是 Java 异常处理的重要子类，各自都包含大量子类。</p><p><strong>Error（错误）:是程序无法处理的错误</strong>，表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关，而表示代码运行时 JVM（Java 虚拟机）出现的问题。例如，Java虚拟机运行错误（Virtual MachineError），当 JVM 不再有继续执行操作所需的内存资源时，将出现 OutOfMemoryError。这些异常发生时，Java虚拟机（JVM）一般会选择线程终止。</p><p>这些错误表示故障发生于虚拟机自身、或者发生在虚拟机试图执行应用时，如Java虚拟机运行错误（Virtual MachineError）、类定义错误（NoClassDefFoundError）等。这些错误是不可查的，因为它们在应用程序的控制和处理能力之 外，而且绝大多数是程序运行时不允许出现的状况。对于设计合理的应用程序来说，即使确实发生了错误，本质上也不应该试图去处理它所引起的异常状况。在 Java中，错误通过Error的子类描述。</p><p><strong>Exception（异常）:是程序本身可以处理的异常</strong>。</font>Exception 类有一个重要的子类 <strong>RuntimeException</strong>。RuntimeException 异常由Java虚拟机抛出。<strong>NullPointerException</strong>（要访问的变量没有引用任何对象时，抛出该异常）、<strong>ArithmeticException</strong>（算术运算异常，一个整数除以0时，抛出该异常）和 <strong>ArrayIndexOutOfBoundsException</strong> （下标越界异常）。</p><p><strong>注意：异常和错误的区别：异常能被程序本身处理，错误是无法处理。</strong></p><h3 id="Throwable类常用方法"><a href="#Throwable类常用方法" class="headerlink" title="Throwable类常用方法"></a>Throwable类常用方法</h3><ul><li><strong>public string getMessage()</strong>:返回异常发生时的详细信息</li><li><strong>public string toString()</strong>:返回异常发生时的简要描述</li><li><strong>public string getLocalizedMessage()</strong>:返回异常对象的本地化信息。使用Throwable的子类覆盖这个方法，可以声称本地化信息。如果子类没有覆盖该方法，则该方法返回的信息与getMessage（）返回的结果相同</li><li><strong>public void printStackTrace()</strong>:在控制台上打印Throwable对象封装的异常信息</li></ul><h3 id="异常处理总结"><a href="#异常处理总结" class="headerlink" title="异常处理总结"></a>异常处理总结</h3><ul><li><strong>try 块：</strong> 用于捕获异常。其后可接零个或多个catch块，如果没有catch块，则必须跟一个finally块。</li><li><strong>catch 块：</strong> 用于处理try捕获到的异常。</li><li><strong>finally 块：</strong> 无论是否捕获或处理异常，finally块里的语句都会被执行。当在try块或catch块中遇到return<br>语句时，finally语句块将在方法返回之前被执行。</li></ul><p><strong>在以下4种特殊情况下，finally块不会被执行：</strong></p><ol><li>在finally语句块第一行发生了异常。 因为在其他行，finally块还是会得到执行</li><li>在前面的代码中用了System.exit(int)已退出程序。 exit是带参函数 ；若该语句在异常语句之后，finally会执行</li><li>程序所在的线程死亡。</li><li>关闭CPU。</li></ol><p><strong>注意：</strong> 当try语句和finally语句中都有return语句时，在方法返回之前，finally语句的内容将被执行，并且finally语句的返回值将会覆盖原始的返回值。如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="type">int</span> <span class="title function_">f</span><span class="params">(<span class="type">int</span> value)</span> &#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> value * value;</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (value == <span class="number">2</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;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>如果调用 <code>f(2)</code>，返回值将是0，因为finally语句的返回值覆盖了try语句块的返回值。</p><h2 id="Java序列化中如果有些字段不想进行序列化，怎么办？"><a href="#Java序列化中如果有些字段不想进行序列化，怎么办？" class="headerlink" title="Java序列化中如果有些字段不想进行序列化，怎么办？"></a>Java序列化中如果有些字段不想进行序列化，怎么办？</h2><p>对于不想进行序列化的变量，使用<code>transient</code>关键字修饰,<code>transient</code>关键字的作用是：阻止实例中那些用此关键字修饰的的变量序列化；当对象被反序列化时，被<code>transient</code>修饰的变量值不会被持久化和恢复。<code>transient</code>只能修饰变量，不能修饰类和方法。</p><h2 id="Java-中-IO-流分为几种？BIO-NIO-AIO有什么区别？"><a href="#Java-中-IO-流分为几种？BIO-NIO-AIO有什么区别？" class="headerlink" title="Java 中 IO 流分为几种？BIO,NIO,AIO有什么区别？"></a>Java 中 IO 流分为几种？<code>BIO</code>,<code>NIO</code>,<code>AIO</code>有什么区别？</h2><h3 id="java-中-IO-流分为几种"><a href="#java-中-IO-流分为几种" class="headerlink" title="java 中 IO 流分为几种?"></a>java 中 IO 流分为几种?</h3><ul><li>按照流的流向分，可以分为输入流和输出流；</li><li>按照操作单元划分，可以划分为字节流和字符流；</li><li>按照流的角色划分为节点流和处理流。</li></ul><p>Java Io流共涉及40多个类，这些类看上去很杂乱，但实际上很有规则，而且彼此之间存在非常紧密的联系， Java I0流的40多个类都是从如下4个抽象类基类中派生出来的。</p><ul><li>InputStream&#x2F;Reader: 所有的输入流的基类，前者是字节输入流，后者是字符输入流。</li><li>OutputStream&#x2F;Writer: 所有输出流的基类，前者是字节输出流，后者是字符输出流。</li></ul><p>按操作方式分类结构图<br><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/27/d53dfa30-c86d-11e9-afb6-c9e9c0891586.png" alt="IO操作方式分类.png"><br>按操作对象分类结构图<br><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/27/e6b45840-c86d-11e9-afb6-c9e9c0891586.png" alt="IO操作对象分类.png"></p><h3 id="BIO-NIO-AIO-有什么区别"><a href="#BIO-NIO-AIO-有什么区别" class="headerlink" title="BIO,NIO,AIO 有什么区别?"></a><code>BIO</code>,<code>NIO</code>,<code>AIO</code> 有什么区别?</h3><ul><li><strong>BIO (Blocking I&#x2F;O):</strong> 同步阻塞I&#x2F;O模式，数据的读取写入必须阻塞在一个线程内等待其完成。在活动连接数不是特别高（小于单机1000）的情况下，这种模型是比较不错的，可以让每一个连接专注于自己的 I&#x2F;O 并且编程模型简单，也不用过多考虑系统的过载、限流等问题。线程池本身就是一个天然的漏斗，可以缓冲一些系统处理不了的连接或请求。但是，当面对十万甚至百万级连接的时候，传统的 BIO 模型是无能为力的。因此，我们需要一种更高效的 I&#x2F;O 处理模型来应对更高的并发量。</li><li><strong>NIO (New I&#x2F;O):</strong> NIO是一种同步非阻塞的I&#x2F;O模型，在Java 1.4 中引入了NIO框架，对应 java.nio 包，提供了 Channel , Selector，Buffer等抽象。NIO中的N可以理解为Non-blocking，不单纯是New。它支持面向缓冲的，基于通道的I&#x2F;O操作方法。 NIO提供了与传统BIO模型中的 <code>Socket</code> 和 <code>ServerSocket</code> 相对应的 <code>SocketChannel</code> 和 <code>ServerSocketChannel</code> 两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样，比较简单，但是性能和可靠性都不好；非阻塞模式正好与之相反。对于低负载、低并发的应用程序，可以使用同步阻塞I&#x2F;O来提升开发速率和更好的维护性；对于高负载、高并发的（网络）应用，应使用 NIO 的非阻塞模式来开发</li><li><strong>AIO (Asynchronous I&#x2F;O):</strong> AIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改进版 NIO 2,它是异步非阻塞的IO模型。异步 IO 是基于事件和回调机制实现的，也就是应用操作之后会直接返回，不会堵塞在那里，当后台处理完成，操作系统会通知相应的线程进行后续的操作。AIO 是异步IO的缩写，虽然 NIO 在网络操作中，提供了非阻塞的方法，但是 NIO 的 IO 行为还是同步的。对于 NIO 来说，我们的业务线程是在 IO 操作准备好时，得到通知，接着就由这个线程自行进行 IO 操作，IO操作本身是同步的。查阅网上相关资料，我发现就目前来说 AIO 的应用还不是很广泛，Netty 之前也尝试使用过 AIO，不过又放弃了。</li></ul><h1 id="编码与实践"><a href="#编码与实践" class="headerlink" title="编码与实践"></a>编码与实践</h1><h2 id="正确使用-equals-方法"><a href="#正确使用-equals-方法" class="headerlink" title="正确使用 equals() 方法"></a>正确使用 <code>equals()</code> 方法</h2><p>Object的equals方法容易抛空指针异常，应使用常量或确定有值的对象来调用 equals。 </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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 不能使用一个值为null的引用类型变量来调用非静态方法，否则会抛出异常</span></span><br><span class="line"><span class="type">String</span> <span class="variable">str</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line"><span class="keyword">if</span> (str.equals(<span class="string">&quot;SnailClimb&quot;</span>)) &#123;</span><br><span class="line">  <span class="comment">// Do sth...</span></span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">  <span class="comment">// Do sth...</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>运行上面的程序会抛出空指针异常，但是我们把第二行的条件判断语句改为下面这样的话，就不会抛出空指针异常，else 语句块得到执行。：</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="string">&quot;SnailClimb&quot;</span>.equals(str);<span class="comment">// false </span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>不过更推荐使用 <code>java.util.Objects#equals</code>(JDK7 引入的工具类)。</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">Objects.equals(<span class="literal">null</span>,<span class="string">&quot;SnailClimb&quot;</span>);<span class="comment">// false</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>我们看一下<code>java.util.Objects#equals</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="type">boolean</span> <span class="title function_">equals</span><span class="params">(Object a, Object b)</span> &#123;</span><br><span class="line"><span class="comment">// 可以避免空指针异常。如果a==null的话此时a.equals(b)就不会得到执行，避免出现空指针异常。</span></span><br><span class="line">    <span class="keyword">return</span> (a == b) || (a != <span class="literal">null</span> &amp;&amp; a.equals(b));</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><strong>注意：</strong></p><ul><li>每种原始类型都有默认值一样，如<code>int</code>默认值为 <code>0</code>，<code>boolean</code> 的默认值为 <code>false</code>，<code>null</code> 是任何引用类型的默认值，不严格的说是所有 <code>Object</code> 类型的默认值</li><li>可以使用<code>==</code>或者<code>!=</code>操作来比较<code>null</code>值，但是不能使用其他算法或者逻辑操作。在Java中<code>null==null</code>将返回<code>true</code></li><li>不能使用一个值为<code>null</code>的引用类型变量来调用非静态方法，否则会抛出异常</li></ul><h2 id="整形包装类值的比较"><a href="#整形包装类值的比较" class="headerlink" title="整形包装类值的比较"></a>整形包装类值的比较</h2><p>所有整形包装类对象值得比较必须使用equals方法。</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></pre></td><td class="code"><pre><span class="line"><span class="type">Integer</span> <span class="variable">x</span> <span class="operator">=</span> <span class="number">3</span>;</span><br><span class="line"><span class="type">Integer</span> <span class="variable">y</span> <span class="operator">=</span> <span class="number">3</span>;</span><br><span class="line">System.out.println(x == y);<span class="comment">// true</span></span><br><span class="line"><span class="type">Integer</span> <span class="variable">a</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Integer</span>(<span class="number">3</span>);</span><br><span class="line"><span class="type">Integer</span> <span class="variable">b</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Integer</span>(<span class="number">3</span>);</span><br><span class="line">System.out.println(a == b);<span class="comment">//false</span></span><br><span class="line">System.out.println(a.equals(b));<span class="comment">//true</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>当使用自动装箱方式创建一个<code>Integer</code>对象时，当数值在<code>-128~127</code>时，会将创建的 <code>Integer</code>对象缓存起来，当下次再出现该数值时，直接从缓存中取出对应的<code>Integer</code>对象。所以上述代码中，<code>x</code>和<code>y</code>引用的是相同的<code>Integer</code>对象。</p><p><strong>注意：</strong>如果你的IDE(IDEA&#x2F;Eclipse&#x2F;MyEclipse)上安装了阿里巴巴的p3c插件，这个插件如果检测到你用 &#x3D;&#x3D;的话会报错提示，推荐安装一个这个插件，很不错。</p><h2 id="BigDecimal"><a href="#BigDecimal" class="headerlink" title="BigDecimal"></a>BigDecimal</h2><h3 id="BigDecimal-的用处"><a href="#BigDecimal-的用处" class="headerlink" title="BigDecimal 的用处"></a>BigDecimal 的用处</h3><p><em><strong>《阿里巴巴Java开发手册》</strong></em> 中提到：<strong>浮点数之间的等值判断，基本数据类型不能用&#x3D;&#x3D;来比较，包装数据类型不能用 equals 来判断。</strong> 具体原理和浮点数的编码方式有关，这里就不多提了，我们下面直接上实例：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">float</span> <span class="variable">a</span> <span class="operator">=</span> <span class="number">1.0f</span> - <span class="number">0.9f</span>;</span><br><span class="line"><span class="type">float</span> <span class="variable">b</span> <span class="operator">=</span> <span class="number">0.9f</span> - <span class="number">0.8f</span>;</span><br><span class="line">System.out.println(a);<span class="comment">// 0.100000024</span></span><br><span class="line">System.out.println(b);<span class="comment">// 0.099999964</span></span><br><span class="line">System.out.println(a == b);<span class="comment">// false</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>具有基本数学知识的我们很清楚的知道输出并不是我们想要的结果（<strong>精度丢失</strong>），我们如何解决这个问题呢？一种很常用的方法是：<strong>使用使用 BigDecimal 来定义浮点数的值，再进行浮点数的运算操作。</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">BigDecimal</span> <span class="variable">a</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">BigDecimal</span>(<span class="string">&quot;1.0&quot;</span>);</span><br><span class="line"><span class="type">BigDecimal</span> <span class="variable">b</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">BigDecimal</span>(<span class="string">&quot;0.9&quot;</span>);</span><br><span class="line"><span class="type">BigDecimal</span> <span class="variable">c</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">BigDecimal</span>(<span class="string">&quot;0.8&quot;</span>);</span><br><span class="line"><span class="type">BigDecimal</span> <span class="variable">x</span> <span class="operator">=</span> a.subtract(b);<span class="comment">// 0.1</span></span><br><span class="line"><span class="type">BigDecimal</span> <span class="variable">y</span> <span class="operator">=</span> b.subtract(c);<span class="comment">// 0.1</span></span><br><span class="line">System.out.println(x.equals(y));<span class="comment">// true </span></span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="BigDecimal-的大小比较"><a href="#BigDecimal-的大小比较" class="headerlink" title="BigDecimal 的大小比较"></a>BigDecimal 的大小比较</h3><p><code>a.compareTo(b)</code> : 返回 -1 表示小于，0 表示 等于， 1表示 大于。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">BigDecimal</span> <span class="variable">a</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">BigDecimal</span>(<span class="string">&quot;1.0&quot;</span>);</span><br><span class="line"><span class="type">BigDecimal</span> <span class="variable">b</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">BigDecimal</span>(<span class="string">&quot;0.9&quot;</span>);</span><br><span class="line">System.out.println(a.compareTo(b));<span class="comment">// 1</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="BigDecimal-保留几位小数"><a href="#BigDecimal-保留几位小数" class="headerlink" title="BigDecimal 保留几位小数"></a>BigDecimal 保留几位小数</h3><p>通过 <code>setScale</code>方法设置保留几位小数以及保留规则。保留规则有挺多种，不需要记，IDEA会提示。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">BigDecimal</span> <span class="variable">m</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">BigDecimal</span>(<span class="string">&quot;1.255433&quot;</span>);</span><br><span class="line"><span class="type">BigDecimal</span> <span class="variable">n</span> <span class="operator">=</span> m.setScale(<span class="number">3</span>,BigDecimal.ROUND_HALF_DOWN);</span><br><span class="line">System.out.println(n);<span class="comment">// 1.255</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="BigDecimal-的使用注意事项"><a href="#BigDecimal-的使用注意事项" class="headerlink" title="BigDecimal 的使用注意事项"></a>BigDecimal 的使用注意事项</h3><p>注意：我们在使用BigDecimal时，为了防止精度丢失，推荐使用它的 <strong>BigDecimal(String)</strong> 构造方法来创建对象。《阿里巴巴Java开发手册》对这部分内容也有提到如下图所示。</p><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/27/55cd6fd0-c870-11e9-afb6-c9e9c0891586.png" alt="BigDecimal.png"></p><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><ul><li>BigDecimal 主要用来操作（大）浮点数，BigInteger 主要用来操作大整数（超过 long 类型）</li><li>BigDecimal 的实现利用到了 BigInteger, 所不同的是 BigDecimal 加入了小数位的概念</li></ul><h2 id="基本数据类型与包装数据类型的使用标准"><a href="#基本数据类型与包装数据类型的使用标准" class="headerlink" title="基本数据类型与包装数据类型的使用标准"></a>基本数据类型与包装数据类型的使用标准</h2><p><em><strong>Reference:《阿里巴巴Java开发手册》</strong></em></p><ul><li>【强制】所有的 POJO 类属性必须使用包装数据类型。</li><li>【强制】RPC 方法的返回值和参数必须使用包装数据类型。</li><li>【推荐】所有的局部变量使用基本数据类型。</li></ul><p>比如我们如果自定义了一个<code>Student</code>类,其中有一个属性是成绩<code>score</code>,如果用<code>Integer</code>而不用<code>int</code>定义,一次考试,学生可能没考,值是<code>null</code>,也可能考了,但考了<code>0</code>分,值是<code>0</code>,这两个表达的状态明显不一样.</p><p><strong>说明</strong> :POJO类属性没有初值是提醒使用者在需要使用时，必须自己显式地进行赋值，任何 <code>NPE</code>问题，或者入库检查，都由使用者来保证。</p><p><strong>正例</strong> : 数据库的查询结果可能是<code>null</code>，因为自动拆箱，用基本数据类型接收有<code>NPE</code> 风险。</p><p><strong>反例</strong> : 比如显示成交总额涨跌情况，即<code>±x%</code>，<code>x</code>为基本数据类型，调用的<code>RPC</code>服务，调用不成功时，返回的是默认值，页面显示为 <code>0%</code>，这是不合理的，应该显示成中划线。所以包装数据类型的 <code>null</code> 值，能够表示额外的信息，如:远程调用失败，异常退出。</p><blockquote><p>NPE:NullPointerException即空指针异常</p></blockquote><h2 id="集合的转换与注意事项"><a href="#集合的转换与注意事项" class="headerlink" title="集合的转换与注意事项"></a>集合的转换与注意事项</h2><h3 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h3><p><code>Arrays.asList()</code>在平时开发中还是比较常见的，我们可以使用它将一个数组转换为一个List集合。</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">String[] myArray = &#123; <span class="string">&quot;Apple&quot;</span>, <span class="string">&quot;Banana&quot;</span>, <span class="string">&quot;Orange&quot;</span> &#125;； </span><br><span class="line">List&lt;String&gt; myList = Arrays.asList(myArray);</span><br><span class="line"><span class="comment">//上面两个语句等价于下面一条语句</span></span><br><span class="line">List&lt;String&gt; myList = Arrays.asList(<span class="string">&quot;Apple&quot;</span>,<span class="string">&quot;Banana&quot;</span>, <span class="string">&quot;Orange&quot;</span>);</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>JDK 源码对于这个方法的说明：</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="comment">/**</span></span><br><span class="line"><span class="comment"> *返回由指定数组支持的固定大小的列表。此方法作为基于数组和基于集合的API之间的桥梁，与 Collection.toArray()结合使用。返回的List是可序列化并实现RandomAccess接口。</span></span><br><span class="line"><span class="comment"> */</span> </span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> &lt;T&gt; List&lt;T&gt; <span class="title function_">asList</span><span class="params">(T... a)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;(a);</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><strong>阿里巴巴Java 开发手册》对其的描述</strong></p><p><code>Arrays.asList()</code>将数组转换为集合后,底层其实还是数组，《阿里巴巴Java 开发手册》对于这个方法有如下描述：</p><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/27/e2a6f410-c872-11e9-afb6-c9e9c0891586.png" alt="阿里巴巴Java开发手Arrays.asList方法.png"></p><h3 id="使用时的注意事项总结"><a href="#使用时的注意事项总结" class="headerlink" title="使用时的注意事项总结"></a>使用时的注意事项总结</h3><p><strong>传递的数组必须是对象数组，而不是基本类型。</strong> </p><p><code>Arrays.asList()</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">int</span>[] myArray = &#123; <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span> &#125;;</span><br><span class="line"><span class="type">List</span> <span class="variable">myList</span> <span class="operator">=</span> Arrays.asList(myArray);</span><br><span class="line">System.out.println(myList.size());<span class="comment">//1</span></span><br><span class="line">System.out.println(myList.get(<span class="number">0</span>));<span class="comment">//数组地址值</span></span><br><span class="line">System.out.println(myList.get(<span class="number">1</span>));<span class="comment">//报错：ArrayIndexOutOfBoundsException</span></span><br><span class="line"><span class="type">int</span> [] array=(<span class="type">int</span>[]) myList.get(<span class="number">0</span>);</span><br><span class="line">System.out.println(array[<span class="number">0</span>]);<span class="comment">//1</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>当传入一个原生数据类型数组时，<code>Arrays.asList()</code> 的真正得到的参数就不是数组中的元素，而是数组对象本身！此时List 的唯一元素就是这个数组，这也就解释了上面的代码。</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></pre></td><td class="code"><pre><span class="line">Integer[] myArray = &#123; <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span> &#125;;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><strong>使用集合的修改方法:<code>add()</code>、<code>remove()</code>、<code>clear()</code>会抛出异常。</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">List</span> <span class="variable">myList</span> <span class="operator">=</span> Arrays.asList(<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>);</span><br><span class="line">myList.add(<span class="number">4</span>);<span class="comment">//运行时报错：UnsupportedOperationException</span></span><br><span class="line">myList.remove(<span class="number">1</span>);<span class="comment">//运行时报错：UnsupportedOperationException</span></span><br><span class="line">myList.clear();<span class="comment">//运行时报错：UnsupportedOperationException</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p><code>Arrays.asList()</code> 方法返回的并不是 <code>java.util.ArrayList</code> ，而是 <code>java.util.Arrays</code> 的一个内部类,这个内部类并没有实现集合的修改方法或者说并没有重写这些方法。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">List</span> <span class="variable">myList</span> <span class="operator">=</span> Arrays.asList(<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>);</span><br><span class="line">System.out.println(myList.getClass());<span class="comment">//class java.util.Arrays$ArrayList</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>下图是<code>java.util.Arrays$ArrayList</code>的简易源码，我们可以看到这个类重写的方法有哪些。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">ArrayList</span>&lt;E&gt; <span class="keyword">extends</span> <span class="title class_">AbstractList</span>&lt;E&gt;</span><br><span class="line">      <span class="keyword">implements</span> <span class="title class_">RandomAccess</span>, java.io.Serializable</span><br><span class="line">  &#123;</span><br><span class="line">      ...</span><br><span class="line"></span><br><span class="line">      <span class="meta">@Override</span></span><br><span class="line">      <span class="keyword">public</span> E <span class="title function_">get</span><span class="params">(<span class="type">int</span> index)</span> &#123;</span><br><span class="line">        ...</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      <span class="meta">@Override</span></span><br><span class="line">      <span class="keyword">public</span> E <span class="title function_">set</span><span class="params">(<span class="type">int</span> index, E element)</span> &#123;</span><br><span class="line">        ...</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      <span class="meta">@Override</span></span><br><span class="line">      <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">indexOf</span><span class="params">(Object o)</span> &#123;</span><br><span class="line">        ...</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      <span class="meta">@Override</span></span><br><span class="line">      <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">contains</span><span class="params">(Object o)</span> &#123;</span><br><span class="line">         ...</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      <span class="meta">@Override</span></span><br><span class="line">      <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">forEach</span><span class="params">(Consumer&lt;? <span class="built_in">super</span> E&gt; action)</span> &#123;</span><br><span class="line">        ...</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      <span class="meta">@Override</span></span><br><span class="line">      <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">replaceAll</span><span class="params">(UnaryOperator&lt;E&gt; operator)</span> &#123;</span><br><span class="line">        ...</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      <span class="meta">@Override</span></span><br><span class="line">      <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">sort</span><span class="params">(Comparator&lt;? <span class="built_in">super</span> E&gt; c)</span> &#123;</span><br><span class="line">        ...</span><br><span class="line">      &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>我们再看一下<code>java.util.AbstractList</code>的<code>remove()</code>方法，这样我们就明白为啥会抛出<code>UnsupportedOperationException</code>。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> E <span class="title function_">remove</span><span class="params">(<span class="type">int</span> index)</span> &#123;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">UnsupportedOperationException</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="如何正确的将数组转换为ArrayList？"><a href="#如何正确的将数组转换为ArrayList？" class="headerlink" title="如何正确的将数组转换为ArrayList？"></a>如何正确的将数组转换为ArrayList？</h3><p><strong>1. 自己动手实现（教育目的）</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//JDK1.5+</span></span><br><span class="line"><span class="keyword">static</span> &lt;T&gt; List&lt;T&gt; <span class="title function_">arrayToList</span><span class="params">(<span class="keyword">final</span> T[] array)</span> &#123;</span><br><span class="line">  <span class="keyword">final</span> List&lt;T&gt; l = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;T&gt;(array.length);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">final</span> T s : array) &#123;</span><br><span class="line">    l.add(s);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> (l);</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Integer [] myArray = &#123; <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span> &#125;;</span><br><span class="line">System.out.println(arrayToList(myArray).getClass());<span class="comment">//class java.util.ArrayList</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p><strong>2. 最简便的方法(推荐)</strong></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="type">List</span> <span class="variable">list</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;(Arrays.asList(<span class="string">&quot;a&quot;</span>, <span class="string">&quot;b&quot;</span>, <span class="string">&quot;c&quot;</span>));</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><strong>3. 使用 Java8 的Stream(推荐)</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">Integer [] myArray = &#123; <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span> &#125;;</span><br><span class="line"><span class="type">List</span> <span class="variable">myList</span> <span class="operator">=</span> Arrays.stream(myArray).collect(Collectors.toList());</span><br><span class="line"><span class="comment">//基本类型也可以实现转换（依赖boxed的装箱操作）</span></span><br><span class="line"><span class="type">int</span> [] myArray2 = &#123; <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span> &#125;;</span><br><span class="line"><span class="type">List</span> <span class="variable">myList</span> <span class="operator">=</span> Arrays.stream(myArray2).boxed().collect(Collectors.toList());</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><strong>4. 使用 Guava(推荐)</strong></p><p>对于不可变集合，你可以使用<a href="https://github.com/google/guava/blob/master/guava/src/com/google/common/collect/ImmutableList.java"><code>ImmutableList</code></a>类及其<a href="https://github.com/google/guava/blob/master/guava/src/com/google/common/collect/ImmutableList.java#L101"><code>of()</code></a>与<a href="https://github.com/google/guava/blob/master/guava/src/com/google/common/collect/ImmutableList.java#L225"><code>copyOf()</code></a>工厂方法：（参数不能为空）</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">List&lt;String&gt; il = ImmutableList.of(<span class="string">&quot;string&quot;</span>, <span class="string">&quot;elements&quot;</span>);  <span class="comment">// from varargs</span></span><br><span class="line">List&lt;String&gt; il = ImmutableList.copyOf(aStringArray);      <span class="comment">// from array</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>对于可变集合，你可以使用<a href="https://github.com/google/guava/blob/master/guava/src/com/google/common/collect/Lists.java"><code>Lists</code></a>类及其<a href="https://github.com/google/guava/blob/master/guava/src/com/google/common/collect/Lists.java#L87"><code>newArrayList()</code></a>工厂方法：</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">List&lt;String&gt; l1 = Lists.newArrayList(anotherListOrCollection);    <span class="comment">// from collection</span></span><br><span class="line">List&lt;String&gt; l2 = Lists.newArrayList(aStringArray);               <span class="comment">// from array</span></span><br><span class="line">List&lt;String&gt; l3 = Lists.newArrayList(<span class="string">&quot;or&quot;</span>, <span class="string">&quot;string&quot;</span>, <span class="string">&quot;elements&quot;</span>); <span class="comment">// from varargs</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p><strong>5. 使用 Apache Commons Collections</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">List&lt;String&gt; list = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;String&gt;();</span><br><span class="line">CollectionUtils.addAll(list, str);</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="Collection-toArray-方法使用的坑与如何反转数组"><a href="#Collection-toArray-方法使用的坑与如何反转数组" class="headerlink" title="Collection.toArray()方法使用的坑与如何反转数组"></a>Collection.toArray()方法使用的坑与如何反转数组</h3><p>该方法是一个泛型方法：<code>&lt;T&gt; T[] toArray(T[] a);</code> 如果<code>toArray</code>方法中没有传递任何参数的话返回的是<code>Object</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></pre></td><td class="code"><pre><span class="line">String [] s= <span class="keyword">new</span> <span class="title class_">String</span>[]&#123;</span><br><span class="line">    <span class="string">&quot;dog&quot;</span>, <span class="string">&quot;lazy&quot;</span>, <span class="string">&quot;a&quot;</span>, <span class="string">&quot;over&quot;</span>, <span class="string">&quot;jumps&quot;</span>, <span class="string">&quot;fox&quot;</span>, <span class="string">&quot;brown&quot;</span>, <span class="string">&quot;quick&quot;</span>, <span class="string">&quot;A&quot;</span></span><br><span class="line">&#125;;</span><br><span class="line">List&lt;String&gt; list = Arrays.asList(s);</span><br><span class="line">Collections.reverse(list);</span><br><span class="line">s=list.toArray(<span class="keyword">new</span> <span class="title class_">String</span>[<span class="number">0</span>]);<span class="comment">//没有指定类型的话会报错</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>由于JVM优化，<code>new String[0]</code>作为<code>Collection.toArray()</code>方法的参数现在使用更好，<code>new String[0]</code>就是起一个模板的作用，指定了返回数组的类型，0是为了节省空间，因为它只是为了说明返回的类型。</p><h3 id="不要在-foreach-循环里进行元素的-remove-add-操作"><a href="#不要在-foreach-循环里进行元素的-remove-add-操作" class="headerlink" title="不要在 foreach 循环里进行元素的 remove&#x2F;add 操作"></a>不要在 foreach 循环里进行元素的 remove&#x2F;add 操作</h3><p>如果要进行<code>remove</code>操作，可以调用迭代器的 <code>remove </code>方法而不是集合类的 remove 方法。因为如果列表在任何时间从结构上修改创建迭代器之后，以任何方式除非通过迭代器自身<code>remove/add</code>方法，迭代器都将抛出一个<code>ConcurrentModificationException</code>,这就是单线程状态下产生的 <strong>fail-fast 机制</strong>。</p><blockquote><p><strong>fail-fast 机制</strong> ：多个线程对 fail-fast 集合进行修改的时，可能会抛出ConcurrentModificationException，单线程下也会出现这种情况，上面已经提到过。</p></blockquote><p><code>java.util</code>包下面的所有的集合类都是fail-fast的，而<code>java.util.concurrent</code>包下面的所有的类都是fail-safe的。</p><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/27/27488890-c873-11e9-afb6-c9e9c0891586.png" alt="foreachremove_add.png"></p><h2 id="泛型的实际应用"><a href="#泛型的实际应用" class="headerlink" title="泛型的实际应用"></a>泛型的实际应用</h2><h3 id="实现最小值函数"><a href="#实现最小值函数" class="headerlink" title="实现最小值函数"></a>实现最小值函数</h3><p>自己设计一个泛型的获取数组最小值的函数.并且这个方法只能接受Number的子类并且实现了Comparable接口。</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">//注意：Number并没有实现Comparable</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> &lt;T <span class="keyword">extends</span> <span class="title class_">Number</span> &amp; Comparable&lt;? <span class="built_in">super</span> T&gt;&gt; T <span class="title function_">min</span><span class="params">(T[] values)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (values == <span class="literal">null</span> || values.length == <span class="number">0</span>) <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    <span class="type">T</span> <span class="variable">min</span> <span class="operator">=</span> values[<span class="number">0</span>];</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">1</span>; i &lt; values.length; i++) &#123;</span><br><span class="line">        <span class="keyword">if</span> (min.compareTo(values[i]) &gt; <span class="number">0</span>) min = values[i];</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> min;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>测试：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="variable">minInteger</span> <span class="operator">=</span> min(<span class="keyword">new</span> <span class="title class_">Integer</span>[]&#123;<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>&#125;);<span class="comment">//result:1</span></span><br><span class="line"><span class="type">double</span> <span class="variable">minDouble</span> <span class="operator">=</span> min(<span class="keyword">new</span> <span class="title class_">Double</span>[]&#123;<span class="number">1.2</span>, <span class="number">2.2</span>, -<span class="number">1d</span>&#125;);<span class="comment">//result:-1d</span></span><br><span class="line"><span class="type">String</span> <span class="variable">typeError</span> <span class="operator">=</span> min(<span class="keyword">new</span> <span class="title class_">String</span>[]&#123;<span class="string">&quot;1&quot;</span>,<span class="string">&quot;3&quot;</span>&#125;);<span class="comment">//报错</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="数据结构"><a href="#数据结构" class="headerlink" title="数据结构"></a>数据结构</h2><h3 id="使用数组实现栈"><a href="#使用数组实现栈" class="headerlink" title="使用数组实现栈"></a>使用数组实现栈</h3><p><strong>自己实现一个栈，要求这个栈具有<code>push()</code>、<code>pop()</code>（返回栈顶元素并出栈）、<code>peek()</code> （返回栈顶元素不出栈）、<code>isEmpty()</code>、<code>size()</code>这些基本的方法。</strong></p><p>提示：每次入栈之前先判断栈的容量是否够用，如果不够用就用<code>Arrays.copyOf()</code>进行扩容；</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyStack</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span>[] storage;<span class="comment">//存放栈中元素的数组</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> capacity;<span class="comment">//栈的容量</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> count;<span class="comment">//栈中元素数量</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">GROW_FACTOR</span> <span class="operator">=</span> <span class="number">2</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//TODO：不带初始容量的构造方法。默认容量为8</span></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">MyStack</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.capacity = <span class="number">8</span>;</span><br><span class="line">        <span class="built_in">this</span>.storage=<span class="keyword">new</span> <span class="title class_">int</span>[<span class="number">8</span>];</span><br><span class="line">        <span class="built_in">this</span>.count = <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//TODO：带初始容量的构造方法</span></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">MyStack</span><span class="params">(<span class="type">int</span> initialCapacity)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (initialCapacity &lt; <span class="number">1</span>)</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(<span class="string">&quot;Capacity too small.&quot;</span>);</span><br><span class="line"></span><br><span class="line">        <span class="built_in">this</span>.capacity = initialCapacity;</span><br><span class="line">        <span class="built_in">this</span>.storage = <span class="keyword">new</span> <span class="title class_">int</span>[initialCapacity];</span><br><span class="line">        <span class="built_in">this</span>.count = <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//TODO：入栈</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">push</span><span class="params">(<span class="type">int</span> value)</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (count == capacity) &#123;</span><br><span class="line">            ensureCapacity();</span><br><span class="line">        &#125;</span><br><span class="line">        storage[count++] = value;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//TODO：确保容量大小</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">ensureCapacity</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">newCapacity</span> <span class="operator">=</span> capacity * GROW_FACTOR;</span><br><span class="line">        storage = Arrays.copyOf(storage, newCapacity);</span><br><span class="line">        capacity = newCapacity;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//TODO：返回栈顶元素并出栈</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="title function_">pop</span><span class="params">()</span> &#123;</span><br><span class="line">        count--;</span><br><span class="line">        <span class="keyword">if</span> (count == -<span class="number">1</span>)</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(<span class="string">&quot;Stack is empty.&quot;</span>);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> storage[count];</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//TODO：返回栈顶元素不出栈</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="title function_">peek</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (count == <span class="number">0</span>)&#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(<span class="string">&quot;Stack is empty.&quot;</span>);</span><br><span class="line">        &#125;<span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> storage[count-<span class="number">1</span>];</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//TODO：判断栈是否为空</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">boolean</span> <span class="title function_">isEmpty</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count == <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//TODO：返回栈中元素的个数</span></span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="title function_">size</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> count;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>验证</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><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="type">MyStack</span> <span class="variable">myStack</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MyStack</span>(<span class="number">3</span>);</span><br><span class="line">myStack.push(<span class="number">1</span>);</span><br><span class="line">myStack.push(<span class="number">2</span>);</span><br><span class="line">myStack.push(<span class="number">3</span>);</span><br><span class="line">myStack.push(<span class="number">4</span>);</span><br><span class="line">myStack.push(<span class="number">5</span>);</span><br><span class="line">myStack.push(<span class="number">6</span>);</span><br><span class="line">myStack.push(<span class="number">7</span>);</span><br><span class="line">myStack.push(<span class="number">8</span>);</span><br><span class="line">System.out.println(myStack.peek());<span class="comment">//8</span></span><br><span class="line">System.out.println(myStack.size());<span class="comment">//8</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">8</span>; i++) &#123;</span><br><span class="line">    System.out.println(myStack.pop());</span><br><span class="line">&#125;</span><br><span class="line">System.out.println(myStack.isEmpty());<span class="comment">//true</span></span><br><span class="line">myStack.pop();<span class="comment">//报错：java.lang.IllegalArgumentException: Stack is empty.</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><h1 id="JavaEE知识"><a href="#JavaEE知识" class="headerlink" title="JavaEE知识"></a>JavaEE知识</h1><h2 id="Servlet总结"><a href="#Servlet总结" class="headerlink" title="Servlet总结"></a>Servlet总结</h2><p>在Java Web程序中，<strong>Servlet</strong>主要负责接收用户请求 <code>HttpServletRequest</code>,在<code>doGet()</code>,<code>doPost()</code>中做相应的处理，并将回应<code>HttpServletResponse</code>反馈给用户。<strong>Servlet</strong> 可以设置初始化参数，供Servlet内部使用。一个Servlet类只会有一个实例，在它初始化时调用<code>init()</code>方法，销毁时调用<code>destroy()</code>方法<strong>。</strong>Servlet需要在web.xml中配置（MyEclipse中创建Servlet会自动配置），<strong>一个Servlet可以设置多个URL访问</strong>。<strong>Servlet不是线程安全</strong>，因此要谨慎使用类变量。</p><h2 id="阐述Servlet和CGI的区别"><a href="#阐述Servlet和CGI的区别" class="headerlink" title="阐述Servlet和CGI的区别?"></a>阐述Servlet和CGI的区别?</h2><h3 id="CGI的不足之处"><a href="#CGI的不足之处" class="headerlink" title="CGI的不足之处:"></a>CGI的不足之处:</h3><ol><li>需要为每个请求启动一个操作CGI程序的系统进程。如果请求频繁，这将会带来很大的开销。</li><li>需要为每个请求加载和运行一个CGI程序，这将带来很大的开销 </li><li>需要重复编写处理网络协议的代码以及编码，这些工作都是非常耗时的。</li></ol><h3 id="Servlet的优点"><a href="#Servlet的优点" class="headerlink" title="Servlet的优点:"></a>Servlet的优点:</h3><ol><li>只需要启动一个操作系统进程以及加载一个JVM，大大降低了系统的开销</li><li>如果多个请求需要做同样处理的时候，这时候只需要加载一个类，这也大大降低了开销</li><li>所有动态加载的类可以实现对网络协议以及请求解码的共享，大大降低了工作量。</li><li>Servlet能直接和Web服务器交互，而普通的CGI程序不能。Servlet还能在各个程序之间共享数据，使数据库连接池之类的功能很容易实现。</li></ol><p>补充：Sun Microsystems公司在1996年发布Servlet技术就是为了和CGI进行竞争，Servlet是一个特殊的Java程序，一个基于Java的Web应用通常包含一个或多个Servlet类。Servlet不能够自行创建并执行，它是在Servlet容器中运行的，容器将用户的请求传递给Servlet程序，并将Servlet的响应回传给用户。通常一个Servlet会关联一个或多个JSP页面。以前CGI经常因为性能开销上的问题被诟病，然而Fast CGI早就已经解决了CGI效率上的问题，所以面试的时候大可不必信口开河的诟病CGI，事实上有很多你熟悉的网站都使用了CGI技术。</p><h2 id="Servlet接口中有哪些方法及Servlet生命周期探秘"><a href="#Servlet接口中有哪些方法及Servlet生命周期探秘" class="headerlink" title="Servlet接口中有哪些方法及Servlet生命周期探秘"></a>Servlet接口中有哪些方法及Servlet生命周期探秘</h2><p>Servlet接口定义了5个方法，其中<strong>前三个方法与Servlet生命周期相关</strong>：</p><ul><li><code>void init(ServletConfig config) throws ServletException</code></li><li><code>void service(ServletRequest req, ServletResponse resp) throws ServletException, java.io.IOException</code></li><li><code>void destroy()</code></li><li><code>java.lang.String getServletInfo()</code></li><li><code>ServletConfig getServletConfig()</code></li></ul><p><strong>生命周期：</strong> <strong>Web容器加载Servlet并将其实例化后，Servlet生命周期开始</strong>，容器运行其<strong>init()方法</strong>进行Servlet的初始化；请求到达时调用Servlet的<strong>service()方法</strong>，service()方法会根据需要调用与请求对应的<strong>doGet或doPost</strong>等方法；当服务器关闭或项目被卸载时服务器会将Servlet实例销毁，此时会调用Servlet的<strong>destroy()方法</strong>。<strong>init方法和destroy方法只会执行一次，service方法客户端每次请求Servlet都会执行</strong>。Servlet中有时会用到一些需要初始化与销毁的资源，因此可以把初始化资源的代码放入init方法中，销毁资源的代码放入destroy方法中，这样就不需要每次处理客户端的请求都要初始化与销毁资源。</p><h2 id="get和post请求的区别"><a href="#get和post请求的区别" class="headerlink" title="get和post请求的区别"></a>get和post请求的区别</h2><blockquote><p>网上也有文章说：get和post请求实际上是没有区别，大家可以自行查询相关文章（参考文章：<a href="https://www.cnblogs.com/logsharing/p/8448446.html">https://www.cnblogs.com/logsharing/p/8448446.html</a>，知乎对应的问题链接：<a href="https://www.zhihu.com/question/28586791">get和post区别？</a>）！我下面给出的只是一种常见的答案。</p></blockquote><ol><li>get请求用来从服务器上获得资源，而post是用来向服务器提交数据；</li><li>get将表单中数据按照name&#x3D;value的形式，添加到action 所指向的URL 后面，并且两者使用”?”连接，而各个变量之间使用”&amp;”连接；post是将表单中的数据放在HTTP协议的请求头或消息体中，传递到action所指向URL；</li><li>get传输的数据要受到URL长度限制（最大长度是 2048 个字符）；而post可以传输大量的数据，上传文件通常要使用post方式；</li><li>使用get时参数会显示在地址栏上，如果这些数据不是敏感数据，那么可以使用get；对于敏感数据还是应用使用post；<br>5.get使用MIME类型<code>application/x-www-form-urlencoded</code>的URL编码（也叫百分号编码）文本的格式传递参数，保证被传送的参数由遵循规范的文本组成，例如一个空格的编码是<code>%20</code>。</li></ol><p>补充：GET方式提交表单的典型应用是搜索引擎。GET方式就是被设计为查询用的。</p><p>还有另外一种回答。推荐大家看一下：</p><ul><li><a href="https://www.zhihu.com/question/28586791">https://www.zhihu.com/question/28586791</a></li><li><a href="https://mp.weixin.qq.com/s?__biz=MzI3NzIzMzg3Mw==&mid=100000054&idx=1&sn=71f6c214f3833d9ca20b9f7dcd9d33e4#rd">https://mp.weixin.qq.com/s?__biz=MzI3NzIzMzg3Mw==&amp;mid=100000054&amp;idx=1&amp;sn=71f6c214f3833d9ca20b9f7dcd9d33e4#rd</a></li></ul><h2 id="什么情况下调用doGet-和doPost"><a href="#什么情况下调用doGet-和doPost" class="headerlink" title="什么情况下调用doGet()和doPost()"></a>什么情况下调用doGet()和doPost()</h2><p>Form标签里的method的属性为get时调用doGet()，为post时调用doPost()。</p><h2 id="转发-Forward-和重定向-Redirect-的区别"><a href="#转发-Forward-和重定向-Redirect-的区别" class="headerlink" title="转发(Forward)和重定向(Redirect)的区别"></a>转发(Forward)和重定向(Redirect)的区别</h2><p><strong>转发是服务器行为，重定向是客户端行为。</strong></p><p><strong>转发（Forward）</strong></p><p>通过RequestDispatcher对象的forward（HttpServletRequest request,HttpServletResponse response）方法实现的。RequestDispatcher可以通过HttpServletRequest 的getRequestDispatcher()方法获得。例如下面的代码就是跳转到login_success.jsp页面。</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">request.getRequestDispatcher(<span class="string">&quot;login_success.jsp&quot;</span>).forward(request, response);</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><strong>重定向（Redirect）</strong>  是利用服务器返回的状态码来实现的。客户端浏览器请求服务器的时候，服务器会返回一个状态码。服务器通过 <code>HttpServletResponse</code> 的 <code>setStatus(int status)</code> 方法设置状态码。如果服务器返回301或者302，则浏览器会到新的网址重新请求该资源。</p><ol><li><strong>从地址栏显示来说</strong></li></ol><p>forward是服务器请求资源,服务器直接访问目标地址的URL,把那个URL的响应内容读取过来,然后把这些内容再发给浏览器.浏览器根本不知道服务器发送的内容从哪里来的,所以它的地址栏还是原来的地址.<br>redirect是服务端根据逻辑,发送一个状态码,告诉浏览器重新去请求那个地址.所以地址栏显示的是新的URL.</p><ol start="2"><li><strong>从数据共享来说</strong></li></ol><p>forward:转发页面和转发到的页面可以共享request里面的数据.<br>redirect:不能共享数据.</p><ol start="3"><li><strong>从运用地方来说</strong></li></ol><p>forward:一般用于用户登陆的时候,根据角色转发到相应的模块.<br>redirect:一般用于用户注销登陆时返回主页面和跳转到其它的网站等</p><ol start="4"><li>从效率来说</li></ol><p>forward:高.<br>redirect:低.</p><h2 id="自动刷新-Refresh"><a href="#自动刷新-Refresh" class="headerlink" title="自动刷新(Refresh)"></a>自动刷新(Refresh)</h2><p>自动刷新不仅可以实现一段时间之后自动跳转到另一个页面，还可以实现一段时间之后自动刷新本页面。Servlet中通过HttpServletResponse对象设置Header属性实现自动刷新例如：</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">Response.setHeader(<span class="string">&quot;Refresh&quot;</span>,<span class="string">&quot;5;URL=http://localhost:8080/servlet/example.htm&quot;</span>);</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>其中5为时间，单位为秒。URL指定就是要跳转的页面（如果设置自己的路径，就会实现每过5秒自动刷新本页面一次）</p><h2 id="Servlet与线程安全"><a href="#Servlet与线程安全" class="headerlink" title="Servlet与线程安全"></a>Servlet与线程安全</h2><p><strong>Servlet不是线程安全的，多线程并发的读写会导致数据不同步的问题。</strong> 解决的办法是尽量不要定义name属性，而是要把name变量分别定义在doGet()和doPost()方法内。虽然使用synchronized(name){}语句块可以解决问题，但是会造成线程的等待，不是很科学的办法。<br>注意：多线程的并发的读写Servlet类属性会导致数据不同步。但是如果只是并发地读取属性而不写入，则不存在数据不同步的问题。因此Servlet里的只读属性最好定义为final类型的。</p><h2 id="JSP和Servlet是什么关系"><a href="#JSP和Servlet是什么关系" class="headerlink" title="JSP和Servlet是什么关系"></a>JSP和Servlet是什么关系</h2><p>其实这个问题在上面已经阐述过了，Servlet是一个特殊的Java程序，它运行于服务器的JVM中，能够依靠服务器的支持向浏览器提供显示内容。JSP本质上是Servlet的一种简易形式，JSP会被服务器处理成一个类似于Servlet的Java程序，可以简化页面内容的生成。Servlet和JSP最主要的不同点在于，Servlet的应用逻辑是在Java文件中，并且完全从表示层中的HTML分离开来。而JSP的情况是Java和HTML可以组合成一个扩展名为.jsp的文件。有人说，Servlet就是在Java中写HTML，而JSP就是在HTML中写Java代码，当然这个说法是很片面且不够准确的。JSP侧重于视图，Servlet更侧重于控制逻辑，在MVC架构模式中，JSP适合充当视图（view）而Servlet适合充当控制器（controller）。</p><h2 id="JSP工作原理"><a href="#JSP工作原理" class="headerlink" title="JSP工作原理"></a>JSP工作原理</h2><p>JSP是一种Servlet，但是与HttpServlet的工作方式不太一样。HttpServlet是先由源代码编译为class文件后部署到服务器下，为先编译后部署。而JSP则是先部署后编译。JSP会在客户端第一次请求JSP文件时被编译为HttpJspPage类（接口Servlet的一个子类）。该类会被服务器临时存放在服务器工作目录里面。下面通过实例给大家介绍。<br>工程JspLoginDemo下有一个名为login.jsp的Jsp文件，把工程第一次部署到服务器上后访问这个Jsp文件，我们发现这个目录下多了下图这两个东东。<br>.class文件便是JSP对应的Servlet。编译完毕后再运行class文件来响应客户端请求。以后客户端访问login.jsp的时候，Tomcat将不再重新编译JSP文件，而是直接调用class文件来响应客户端请求。</p><p>由于JSP只会在客户端第一次请求的时候被编译 ，因此第一次请求JSP时会感觉比较慢，之后就会感觉快很多。如果把服务器保存的class文件删除，服务器也会重新编译JSP。</p><p>开发Web程序时经常需要修改JSP。Tomcat能够自动检测到JSP程序的改动。如果检测到JSP源代码发生了改动。Tomcat会在下次客户端请求JSP时重新编译JSP，而不需要重启Tomcat。这种自动检测功能是默认开启的，检测改动会消耗少量的时间，在部署Web应用的时候可以在web.xml中将它关掉。</p><h2 id="JSP有哪些内置对象、作用分别是什么"><a href="#JSP有哪些内置对象、作用分别是什么" class="headerlink" title="JSP有哪些内置对象、作用分别是什么"></a>JSP有哪些内置对象、作用分别是什么</h2><p><a href="http://blog.csdn.net/qq_34337272/article/details/64310849">JSP内置对象 - CSDN博客 </a> </p><p>JSP有9个内置对象：</p><ul><li>request：封装客户端的请求，其中包含来自GET或POST请求的参数；</li><li>response：封装服务器对客户端的响应；</li><li>pageContext：通过该对象可以获取其他对象；</li><li>session：封装用户会话的对象；</li><li>application：封装服务器运行环境的对象；</li><li>out：输出服务器响应的输出流对象；</li><li>config：Web应用的配置对象；</li><li>page：JSP页面本身（相当于Java程序中的this）；</li><li>exception：封装页面抛出异常的对象。</li></ul><h2 id="Request对象的主要方法有哪些"><a href="#Request对象的主要方法有哪些" class="headerlink" title="Request对象的主要方法有哪些"></a>Request对象的主要方法有哪些</h2><ul><li>setAttribute(String name,Object)：设置名字为name的request 的参数值 </li><li>getAttribute(String name)：返回由name指定的属性值 </li><li>getAttributeNames()：返回request 对象所有属性的名字集合，结果是一个枚举的实例 </li><li>getCookies()：返回客户端的所有 Cookie 对象，结果是一个Cookie 数组 </li><li>getCharacterEncoding() ：返回请求中的字符编码方式 &#x3D; getContentLength() ：返回请求的 Body的长度 </li><li>getHeader(String name) ：获得HTTP协议定义的文件头信息 </li><li>getHeaders(String name) ：返回指定名字的request Header 的所有值，结果是一个枚举的实例 </li><li>getHeaderNames() ：返回所以request Header 的名字，结果是一个枚举的实例 </li><li>getInputStream() ：返回请求的输入流，用于获得请求中的数据 </li><li>getMethod() ：获得客户端向服务器端传送数据的方法 </li><li>getParameter(String name) ：获得客户端传送给服务器端的有 name指定的参数值 </li><li>getParameterNames() ：获得客户端传送给服务器端的所有参数的名字，结果是一个枚举的实例 </li><li>getParameterValues(String name)：获得有name指定的参数的所有值 </li><li>getProtocol()：获取客户端向服务器端传送数据所依据的协议名称 </li><li>getQueryString() ：获得查询字符串 </li><li>getRequestURI() ：获取发出请求字符串的客户端地址 </li><li>getRemoteAddr()：获取客户端的 IP 地址 </li><li>getRemoteHost() ：获取客户端的名字 </li><li>getSession([Boolean create]) ：返回和请求相关 Session </li><li>getServerName() ：获取服务器的名字 </li><li>getServletPath()：获取客户端所请求的脚本文件的路径 </li><li>getServerPort()：获取服务器的端口号 </li><li>removeAttribute(String name)：删除请求中的一个属性</li></ul><h2 id="request-getAttribute-和-request-getParameter-有何区别"><a href="#request-getAttribute-和-request-getParameter-有何区别" class="headerlink" title="request.getAttribute()和 request.getParameter()有何区别"></a>request.getAttribute()和 request.getParameter()有何区别</h2><p><strong>从获取方向来看：</strong></p><p><code>getParameter()</code>是获取 POST&#x2F;GET 传递的参数值；</p><p><code>getAttribute()</code>是获取对象容器中的数据值；</p><p><strong>从用途来看：</strong></p><p><code>getParameter()</code>用于客户端重定向时，即点击了链接或提交按扭时传值用，即用于在用表单或url重定向传值时接收数据用。</p><p><code>getAttribute()</code> 用于服务器端重定向时，即在 sevlet 中使用了 forward 函数,或 struts 中使用了<br>mapping.findForward。 getAttribute 只能收到程序用 setAttribute 传过来的值。</p><p>另外，可以用 <code>setAttribute()</code>,<code>getAttribute()</code> 发送接收对象.而 <code>getParameter()</code> 显然只能传字符串。<br><code>setAttribute()</code> 是应用服务器把这个对象放在该页面所对应的一块内存中去，当你的页面服务器重定向到另一个页面时，应用服务器会把这块内存拷贝另一个页面所对应的内存中。这样<code>getAttribute()</code>就能取得你所设下的值，当然这种方法可以传对象。session也一样，只是对象在内存中的生命周期不一样而已。<code>getParameter()</code>只是应用服务器在分析你送上来的 request页面的文本时，取得你设在表单或 url 重定向时的值。</p><p><strong>总结：</strong></p><p><code>getParameter()</code>返回的是String,用于读取提交的表单中的值;（获取之后会根据实际需要转换为自己需要的相应类型，比如整型，日期类型啊等等）</p><p><code>getAttribute()</code>返回的是Object，需进行转换,可用<code>setAttribute()</code>设置成任意对象，使用很灵活，可随时用</p><h2 id="include指令include的行为的区别"><a href="#include指令include的行为的区别" class="headerlink" title="include指令include的行为的区别"></a>include指令include的行为的区别</h2><p><strong>include指令：</strong> JSP可以通过include指令来包含其他文件。被包含的文件可以是JSP文件、HTML文件或文本文件。包含的文件就好像是该JSP文件的一部分，会被同时编译执行。 语法格式如下：<br><code>&lt;%@ include file=&quot;文件相对 url 地址&quot; %&gt;</code></p><p>i<strong>nclude动作：</strong> <code>&lt;jsp:include&gt;</code>动作元素用来包含静态和动态的文件。该动作把指定文件插入正在生成的页面。语法格式如下：<br><code>&lt;jsp:include page=&quot;相对 URL 地址&quot; flush=&quot;true&quot; /&gt;</code></p><h2 id="JSP九大内置对象，七大动作，三大指令"><a href="#JSP九大内置对象，七大动作，三大指令" class="headerlink" title="JSP九大内置对象，七大动作，三大指令"></a>JSP九大内置对象，七大动作，三大指令</h2><p><a href="http://blog.csdn.net/qq_34337272/article/details/64310849">JSP九大内置对象，七大动作，三大指令总结</a></p><h2 id="讲解JSP中的四种作用域"><a href="#讲解JSP中的四种作用域" class="headerlink" title="讲解JSP中的四种作用域"></a>讲解JSP中的四种作用域</h2><p>JSP中的四种作用域包括<code>page</code>、<code>request</code>、<code>session</code>和<code>application</code>，具体来说：</p><ul><li><strong>page</strong>代表与一个页面相关的对象和属性。</li><li><strong>request</strong>代表与Web客户机发出的一个请求相关的对象和属性。一个请求可能跨越多个页面，涉及多个Web组件；需要在页面显示的临时数据可以置于此作用域。</li><li><strong>session</strong>代表与某个用户与服务器建立的一次会话相关的对象和属性。跟某个用户相关的数据应该放在用户自己的session中。</li><li><strong>application</strong>代表与整个Web应用程序相关的对象和属性，它实质上是跨越整个Web应用程序，包括多个页面、请求和会话的一个全局作用域。</li></ul><h2 id="如何实现JSP或Servlet的单线程模式"><a href="#如何实现JSP或Servlet的单线程模式" class="headerlink" title="如何实现JSP或Servlet的单线程模式"></a>如何实现JSP或Servlet的单线程模式</h2><p>对于JSP页面，可以通过page指令进行设置。<br><code>&lt;%@page isThreadSafe=”false”%&gt;</code></p><p>对于Servlet，可以让自定义的Servlet实现SingleThreadModel标识接口。</p><p>说明：如果将JSP或Servlet设置成单线程工作模式，会导致每个请求创建一个Servlet实例，这种实践将导致严重的性能问题（服务器的内存压力很大，还会导致频繁的垃圾回收），所以通常情况下并不会这么做。</p><h2 id="实现会话跟踪的技术有哪些"><a href="#实现会话跟踪的技术有哪些" class="headerlink" title="实现会话跟踪的技术有哪些"></a>实现会话跟踪的技术有哪些</h2><ol><li><strong>使用Cookie</strong></li></ol><p>向客户端发送Cookie</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Cookie</span> <span class="variable">c</span> <span class="operator">=</span><span class="keyword">new</span> <span class="title class_">Cookie</span>(<span class="string">&quot;name&quot;</span>,<span class="string">&quot;value&quot;</span>); <span class="comment">//创建Cookie </span></span><br><span class="line">c.setMaxAge(<span class="number">60</span>*<span class="number">60</span>*<span class="number">24</span>); <span class="comment">//设置最大时效，此处设置的最大时效为一天</span></span><br><span class="line">response.addCookie(c); <span class="comment">//把Cookie放入到HTTP响应中</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>从客户端读取Cookie</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="type">String</span> <span class="variable">name</span> <span class="operator">=</span><span class="string">&quot;name&quot;</span>; </span><br><span class="line">Cookie[]cookies =request.getCookies(); </span><br><span class="line"><span class="keyword">if</span>(cookies !=<span class="literal">null</span>)&#123; </span><br><span class="line">   <span class="keyword">for</span>(<span class="type">int</span> i= <span class="number">0</span>;i&lt;cookies.length;i++)&#123; </span><br><span class="line">    <span class="type">Cookie</span> <span class="variable">cookie</span> <span class="operator">=</span>cookies[i]; </span><br><span class="line">    <span class="keyword">if</span>(name.equals(cookis.getName())) </span><br><span class="line">    <span class="comment">//something is here. </span></span><br><span class="line">    <span class="comment">//you can get the value </span></span><br><span class="line">    cookie.getValue(); </span><br><span class="line">       </span><br><span class="line">   &#125;</span><br><span class="line"> &#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><strong>优点:</strong> 数据可以持久保存，不需要服务器资源，简单，基于文本的Key-Value</p><p><strong>缺点:</strong> 大小受到限制，用户可以禁用Cookie功能，由于保存在本地，有一定的安全风险。</p><ol start="2"><li><strong>URL 重写</strong></li></ol><p>在URL中添加用户会话的信息作为请求的参数，或者将唯一的会话ID添加到URL结尾以标识一个会话。 </p><p><strong>优点：</strong> 在Cookie被禁用的时候依然可以使用</p><p><strong>缺点：</strong> 必须对网站的URL进行编码，所有页面必须动态生成，不能用预先记录下来的URL进行访问。</p><ol start="3"><li><strong>隐藏的表单域</strong><figure class="highlight html"><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="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">&quot;hidden&quot;</span> <span class="attr">name</span> =<span class="string">&quot;session&quot;</span> <span class="attr">value</span>=<span class="string">&quot;...&quot;</span>/&gt;</span></span><br><span class="line"></span><br></pre></td></tr></table></figure></li></ol><p><strong>优点：</strong> Cookie被禁时可以使用</p><p><strong>缺点：</strong> 所有页面必须是表单提交之后的结果。</p><ol start="4"><li><strong>HttpSession</strong></li></ol><p>在所有会话跟踪技术中，HttpSession对象是最强大也是功能最多的。当一个用户第一次访问某个网站时会自动创建 HttpSession，每个用户可以访问他自己的HttpSession。可以通过HttpServletRequest对象的getSession方 法获得HttpSession，通过HttpSession的setAttribute方法可以将一个值放在HttpSession中，通过调用 HttpSession对象的getAttribute方法，同时传入属性名就可以获取保存在HttpSession中的对象。与上面三种方式不同的 是，HttpSession放在服务器的内存中，因此不要将过大的对象放在里面，即使目前的Servlet容器可以在内存将满时将HttpSession 中的对象移到其他存储设备中，但是这样势必影响性能。添加到HttpSession中的值可以是任意Java对象，这个对象最好实现了 Serializable接口，这样Servlet容器在必要的时候可以将其序列化到文件中，否则在序列化时就会出现异常。</p><h2 id="Cookie和Session的的区别"><a href="#Cookie和Session的的区别" class="headerlink" title="Cookie和Session的的区别"></a>Cookie和Session的的区别</h2><p>Cookie 和 Session都是用来跟踪浏览器用户身份的会话方式，但是两者的应用场景不太一样。</p><p><strong>Cookie 一般用来保存用户信息</strong><br>比如:</p><ol><li>我们在 Cookie 中保存已经登录过得用户信息，下次访问网站的时候页面可以自动帮你登录的一些基本信息给填了；</li><li>一般的网站都会有保持登录也就是说下次你再访问网站的时候就不需要重新登录了，这是因为用户登录的时候我们可以存放了一个 Token 在 Cookie 中，下次登录的时候只需要根据 Token 值来查找用户即可(为了安全考虑，重新登录一般要将 Token 重写)；</li><li>登录一次网站后访问网站其他页面不需要重新登录。<strong>Session 的主要作用就是通过服务端记录用户的状态。</strong> 典型的场景是购物车，当你要添加商品到购物车的时候，系统不知道是哪个用户操作的，因为 HTTP 协议是无状态的。服务端给特定的用户创建特定的 Session 之后就可以标识这个用户并且跟踪这个用户了。</li></ol><p>Cookie 数据保存在客户端(浏览器端)，Session 数据保存在服务器端。</p><p>Cookie 存储在客户端中，而Session存储在服务器上，相对来说 Session 安全性更高。如果使用 Cookie 的一些敏感信息不要写入 Cookie 中，最好能将 Cookie 信息加密然后使用到的时候再去服务器端解密。</p><h1 id="容器"><a href="#容器" class="headerlink" title="容器"></a>容器</h1><h2 id="说说List-Set-Map三者的区别？"><a href="#说说List-Set-Map三者的区别？" class="headerlink" title="说说List,Set,Map三者的区别？"></a>说说List,Set,Map三者的区别？</h2><ul><li><strong>List(对付顺序的好帮手)：</strong> List接口存储一组不唯一（可以有多个元素引用相同的对象），有序的对象</li><li><strong>Set(注重独一无二的性质):</strong> 不允许重复的集合。不会有多个元素引用相同的对象。</li><li><strong>Map(用Key来搜索的专家):</strong> 使用键值对存储。Map会维护与Key有关联的值。两个Key可以引用相同的对象，但Key不能重复，典型的Key是String类型，但也可以是任何对象。</li></ul><h2 id="Arraylist-与-LinkedList-区别"><a href="#Arraylist-与-LinkedList-区别" class="headerlink" title="Arraylist 与 LinkedList 区别?"></a>Arraylist 与 LinkedList 区别?</h2><ul><li><strong>1. 是否保证线程安全：</strong> <code>ArrayList</code> 和 <code>LinkedList</code> 都是不同步的，也就是不保证线程安全；</li><li><strong>2. 底层数据结构：</strong> <code>Arraylist</code> 底层使用的是 <strong><code>Object</code> 数组</strong>；<code>LinkedList</code> 底层使用的是 <strong>双向链表</strong> 数据结构（JDK1.6之前为循环链表，JDK1.7取消了循环。注意双向链表和双向循环链表的区别，下面有介绍到！）</li><li><strong>3. 插入和删除是否受元素位置的影响：</strong> <ol><li><strong><code>ArrayList</code> 采用数组存储，所以插入和删除元素的时间复杂度受元素位置的影响。</strong> 比如：执行<code>add(E e) </code>方法的时候， <code>ArrayList</code> 会默认在将指定的元素追加到此列表的末尾，这种情况时间复杂度就是O(1)。但是如果要在指定位置 i 插入和删除元素的话（<code>add(int index, E element) </code>）时间复杂度就为 O(n-i)。因为在进行上述操作的时候集合中第 i 和第 i 个元素之后的(n-i)个元素都要执行向后位&#x2F;向前移一位的操作。 </li><li><strong><code>LinkedList</code> 采用链表存储，所以插入，删除元素时间复杂度不受元素位置的影响，都是近似 O（1）而数组为近似 O（n）。</strong></li></ol></li><li><strong>4. 是否支持快速随机访问：</strong><code>LinkedList</code> 不支持高效的随机元素访问，而 <code>ArrayList</code> 支持。快速随机访问就是通过元素的序号快速获取元素对象(对应于<code>get(int index) </code>方法)。</li><li><strong>5. 内存空间占用：</strong>ArrayList的空 间浪费主要体现在在list列表的结尾会预留一定的容量空间，而LinkedList的空间花费则体现在它的每一个元素都需要消耗比ArrayList更多的空间（因为要存放直接后继和直接前驱以及数据）。</li></ul><h3 id="补充内容-RandomAccess接口"><a href="#补充内容-RandomAccess接口" class="headerlink" title="补充内容:RandomAccess接口"></a>补充内容:RandomAccess接口</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">RandomAccess</span> &#123;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>查看源码我们发现实际上 <code>RandomAccess</code> 接口中什么都没有定义。所以，在我看来 <code>RandomAccess</code> 接口不过是一个标识罢了。标识什么？ 标识实现这个接口的类具有随机访问功能。</p><p>在 <code>binarySearch（</code>）方法中，它要判断传入的list 是否 <code>RamdomAccess</code> 的实例，如果是，调用<code>indexedBinarySearch（）</code>方法，如果不是，那么调用<code>iteratorBinarySearch（）</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> &lt;T&gt; <span class="type">int</span> <span class="title function_">binarySearch</span><span class="params">(List&lt;? extends Comparable&lt;? <span class="built_in">super</span> T&gt;&gt; list, T key)</span> &#123;</span><br><span class="line"><span class="keyword">if</span> (list <span class="keyword">instanceof</span> RandomAccess || list.size()&lt;BINARYSEARCH_THRESHOLD)</span><br><span class="line"><span class="keyword">return</span> Collections.indexedBinarySearch(list, key);</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line"><span class="keyword">return</span> Collections.iteratorBinarySearch(list, key);</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><code>ArrayList</code> 实现了 <code>RandomAccess</code> 接口， 而 <code>LinkedList</code> 没有实现。为什么呢？我觉得还是和底层数据结构有关！<code>ArrayList</code> 底层是数组，而 <code>LinkedList</code> 底层是链表。数组天然支持随机访问，时间复杂度为 O（1），所以称为快速随机访问。链表需要遍历到特定位置才能访问特定位置的元素，时间复杂度为 O（n），所以不支持快速随机访问。，<code>ArrayList</code> 实现了 <code>RandomAccess</code> 接口，就表明了他具有快速随机访问功能。 <code>RandomAccess</code> 接口只是标识，并不是说 <code>ArrayList</code> 实现 <code>RandomAccess</code> 接口才具有快速随机访问功能的！</p><p><strong>下面再总结一下 list 的遍历方式选择：</strong></p><ul><li>实现了 <code>RandomAccess</code> 接口的list，优先选择普通 for 循环 ，其次 foreach,</li><li>未实现 <code>RandomAccess</code>接口的list，优先选择iterator遍历（foreach遍历底层也是通过iterator实现的,），大size的数据，千万不要使用普通for循环</li></ul><h3 id="补充内容-双向链表和双向循环链表"><a href="#补充内容-双向链表和双向循环链表" class="headerlink" title="补充内容:双向链表和双向循环链表"></a>补充内容:双向链表和双向循环链表</h3><p><strong>双向链表：</strong> 包含两个指针，一个prev指向前一个节点，一个next指向后一个节点。</p><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/27/cf255880-c879-11e9-afb6-c9e9c0891586.png" alt="双向链表.png"></p><p><strong>双向循环链表：</strong> 最后一个节点的 next 指向head，而 head 的prev指向最后一个节点，构成一个环。</p><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/27/d40de830-c879-11e9-afb6-c9e9c0891586.png" alt="双向循环链表.png"></p><h2 id="ArrayList-与-Vector-区别呢-为什么要用Arraylist取代Vector呢？"><a href="#ArrayList-与-Vector-区别呢-为什么要用Arraylist取代Vector呢？" class="headerlink" title="ArrayList 与 Vector 区别呢?为什么要用Arraylist取代Vector呢？"></a>ArrayList 与 Vector 区别呢?为什么要用Arraylist取代Vector呢？</h2><p><code>Vector</code>类的所有方法都是同步的。可以由两个线程安全地访问一个Vector对象、但是一个线程访问Vector的话代码要在同步操作上耗费大量的时间。</p><p><code>Arraylist</code>不是同步的，所以在不需要保证线程安全时建议使用Arraylist。</p><h2 id="ArrayList的扩容机制"><a href="#ArrayList的扩容机制" class="headerlink" title="ArrayList的扩容机制"></a>ArrayList的扩容机制</h2><h3 id="ArrayList的构造函数"><a href="#ArrayList的构造函数" class="headerlink" title="ArrayList的构造函数"></a>ArrayList的构造函数</h3><p><strong>ArrayList有三种方式来初始化，构造方法源码如下：</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 默认初始容量大小</span></span><br><span class="line"><span class="comment"> */</span></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">DEFAULT_CAPACITY</span> <span class="operator">=</span> <span class="number">10</span>;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = &#123;&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> *默认构造函数，使用初始容量10构造一个空列表(无参数构造)</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="title function_">ArrayList</span><span class="params">()</span> &#123;</span><br><span class="line"><span class="built_in">this</span>.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 带初始容量参数的构造函数。（用户自己指定容量）</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="title function_">ArrayList</span><span class="params">(<span class="type">int</span> initialCapacity)</span> &#123;</span><br><span class="line"><span class="keyword">if</span> (initialCapacity &gt; <span class="number">0</span>) &#123;<span class="comment">//初始容量大于0</span></span><br><span class="line"><span class="comment">//创建initialCapacity大小的数组</span></span><br><span class="line"><span class="built_in">this</span>.elementData = <span class="keyword">new</span> <span class="title class_">Object</span>[initialCapacity];</span><br><span class="line">&#125; <span class="keyword">else</span> <span class="keyword">if</span> (initialCapacity == <span class="number">0</span>) &#123;<span class="comment">//初始容量等于0</span></span><br><span class="line"><span class="comment">//创建空数组</span></span><br><span class="line"><span class="built_in">this</span>.elementData = EMPTY_ELEMENTDATA;</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;<span class="comment">//初始容量小于0，抛出异常</span></span><br><span class="line"><span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(<span class="string">&quot;Illegal Capacity: &quot;</span>+</span><br><span class="line">   initialCapacity);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">*构造包含指定collection元素的列表，这些元素利用该集合的迭代器按顺序返回</span></span><br><span class="line"><span class="comment">*如果指定的集合为null，throws NullPointerException。 </span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"> <span class="keyword">public</span> <span class="title function_">ArrayList</span><span class="params">(Collection&lt;? extends E&gt; c)</span> &#123;</span><br><span class="line">elementData = c.toArray();</span><br><span class="line"><span class="keyword">if</span> ((size = elementData.length) != <span class="number">0</span>) &#123;</span><br><span class="line"><span class="comment">// c.toArray might (incorrectly) not return Object[] (see 6260652)</span></span><br><span class="line"><span class="keyword">if</span> (elementData.getClass() != Object[].class)</span><br><span class="line">elementData = Arrays.copyOf(elementData, size, Object[].class);</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line"><span class="comment">// replace with empty array.</span></span><br><span class="line"><span class="built_in">this</span>.elementData = EMPTY_ELEMENTDATA;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>细心的同学一定会发现 ：<strong>以无参数构造方法创建 ArrayList 时，实际上初始化赋值的是一个空数组。当真正对数组进行添加元素操作时，才真正分配容量。即向数组中添加第一个元素时，数组容量扩为10。</strong> 下面在我们分析 ArrayList 扩容时会讲到这一点内容！</p><h3 id="分析ArrayList扩容机制"><a href="#分析ArrayList扩容机制" class="headerlink" title="分析ArrayList扩容机制"></a>分析ArrayList扩容机制</h3><p>这里以无参构造函数创建的 ArrayList 为例分析</p><h4 id="add-方法"><a href="#add-方法" class="headerlink" title="add() 方法"></a><code>add()</code> 方法</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 将指定的元素追加到此列表的末尾。 </span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">add</span><span class="params">(E e)</span> &#123;</span><br><span class="line"><span class="comment">//添加元素之前，先调用ensureCapacityInternal方法</span></span><br><span class="line">ensureCapacityInternal(size + <span class="number">1</span>);  <span class="comment">// Increments modCount!!</span></span><br><span class="line"><span class="comment">//这里看到ArrayList添加元素的实质就相当于为数组赋值</span></span><br><span class="line">elementData[size++] = e;</span><br><span class="line"><span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h4 id="ensureCapacityInternal-方法"><a href="#ensureCapacityInternal-方法" class="headerlink" title="ensureCapacityInternal() 方法"></a><code>ensureCapacityInternal()</code> 方法</h4><p>可以看到 <code>add</code> 方法 首先调用了<code>ensureCapacityInternal(size + 1)</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></pre></td><td class="code"><pre><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_">ensureCapacityInternal</span><span class="params">(<span class="type">int</span> minCapacity)</span> &#123;</span><br><span class="line"><span class="keyword">if</span> (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) &#123;</span><br><span class="line">  <span class="comment">// 获取默认的容量和传入参数的较大值</span></span><br><span class="line">minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">ensureExplicitCapacity(minCapacity);</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><strong>当要 add 进第1个元素时，minCapacity为1，在Math.max()方法比较后，minCapacity 为10。</strong></p><h4 id="ensureExplicitCapacity-方法"><a href="#ensureExplicitCapacity-方法" class="headerlink" title="ensureExplicitCapacity() 方法"></a><code>ensureExplicitCapacity()</code> 方法</h4><p>如果调用 <code>ensureCapacityInternal()</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></pre></td><td class="code"><pre><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_">ensureExplicitCapacity</span><span class="params">(<span class="type">int</span> minCapacity)</span> &#123;</span><br><span class="line">modCount++;</span><br><span class="line"></span><br><span class="line"><span class="comment">// overflow-conscious code</span></span><br><span class="line"><span class="keyword">if</span> (minCapacity - elementData.length &gt; <span class="number">0</span>)</span><br><span class="line"><span class="comment">//调用grow方法进行扩容，调用此方法代表已经开始扩容了</span></span><br><span class="line">grow(minCapacity);</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>我们来仔细分析一下：</p><ul><li>当我们要 add 进第1个元素到 ArrayList 时，elementData.length 为0 （因为还是一个空的 list），因为执行了 <code>ensureCapacityInternal()</code> 方法 ，所以 minCapacity 此时为10。此时，<code>minCapacity - elementData.length &gt; 0 </code>成立，所以会进入 <code>grow(minCapacity)</code> 方法。</li><li>当add第2个元素时，minCapacity 为2，此时e lementData.length(容量)在添加第一个元素后扩容成 10 了。此时，<code>minCapacity - elementData.length &gt; 0 </code> 不成立，所以不会进入 （执行）<code>grow(minCapacity)</code> 方法。</li><li>添加第3、4···到第10个元素时，依然不会执行grow方法，数组容量都为10。</li></ul><p>直到添加第11个元素，minCapacity(为11)比elementData.length（为10）要大。进入grow方法进行扩容。</p><h4 id="grow-方法"><a href="#grow-方法" class="headerlink" title="grow() 方法"></a><code>grow()</code> 方法</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 要分配的最大数组大小</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">MAX_ARRAY_SIZE</span> <span class="operator">=</span> Integer.MAX_VALUE - <span class="number">8</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * ArrayList扩容的核心方法。</span></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_">grow</span><span class="params">(<span class="type">int</span> minCapacity)</span> &#123;</span><br><span class="line"><span class="comment">// oldCapacity为旧容量，newCapacity为新容量</span></span><br><span class="line"><span class="type">int</span> <span class="variable">oldCapacity</span> <span class="operator">=</span> elementData.length;</span><br><span class="line"><span class="comment">//将oldCapacity 右移一位，其效果相当于oldCapacity /2，</span></span><br><span class="line"><span class="comment">//我们知道位运算的速度远远快于整除运算，整句运算式的结果就是将新容量更新为旧容量的1.5倍，</span></span><br><span class="line"><span class="type">int</span> <span class="variable">newCapacity</span> <span class="operator">=</span> oldCapacity + (oldCapacity &gt;&gt; <span class="number">1</span>);</span><br><span class="line"><span class="comment">//然后检查新容量是否大于最小需要容量，若还是小于最小需要容量，那么就把最小需要容量当作数组的新容量，</span></span><br><span class="line"><span class="keyword">if</span> (newCapacity - minCapacity &lt; <span class="number">0</span>)</span><br><span class="line">newCapacity = minCapacity;</span><br><span class="line">   <span class="comment">// 如果新容量大于 MAX_ARRAY_SIZE,进入(执行) `hugeCapacity()` 方法来比较 minCapacity 和 MAX_ARRAY_SIZE，</span></span><br><span class="line">   <span class="comment">//如果minCapacity大于最大容量，则新容量则为`Integer.MAX_VALUE`，否则，新容量大小则为 MAX_ARRAY_SIZE 即为 `Integer.MAX_VALUE - 8`。</span></span><br><span class="line"><span class="keyword">if</span> (newCapacity - MAX_ARRAY_SIZE &gt; <span class="number">0</span>)</span><br><span class="line">newCapacity = hugeCapacity(minCapacity);</span><br><span class="line"><span class="comment">// minCapacity is usually close to size, so this is a win:</span></span><br><span class="line">elementData = Arrays.copyOf(elementData, newCapacity);</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><strong>int newCapacity &#x3D; oldCapacity + (oldCapacity &gt;&gt; 1),所以 ArrayList 每次扩容之后容量都会变为原来的 1.5 倍！（JDK1.6版本以后）</strong>  JDk1.6版本时，扩容之后容量为 1.5 倍+1！详情请参考源码</p><blockquote><p><code>&gt;&gt;</code>（移位运算符）：<code>&gt;&gt;1</code> 右移一位相当于<code>除以2</code>，<code>右移n位</code>相当于<code>除以 2 的 n 次方</code>。这里 <code>oldCapacity 右移1位</code> 所以相当于<code>oldCapacity / 2</code>。<br>对于大数据的2进制运算,位移运算符比那些普通运算符的运算要快很多,因为程序仅仅移动一下而已,不去计算,这样提高了效率,节省了资源 　</p></blockquote><p><strong>我们再来通过例子探究一下<code>grow()</code> 方法 ：</strong></p><ul><li>当add第1个元素时，oldCapacity 为0，经比较后第一个if判断成立，newCapacity &#x3D; minCapacity(为10)。但是第二个if判断不会成立，即newCapacity 不比 MAX_ARRAY_SIZE大，则不会进入 <code>hugeCapacity</code> 方法。数组容量为10，add方法中 return true,size增为1。</li><li>当add第11个元素进入grow方法时，newCapacity为15，比minCapacity（为11）大，第一个if判断不成立。新容量没有大于数组最大size，不会进入hugeCapacity方法。数组容量扩为15，add方法中return true,size增为11。</li><li>以此类推······</li></ul><p><strong>这里补充一点比较重要，但是容易被忽视掉的知识点：</strong></p><ul><li>java 中的 <code>length </code>属性是针对数组说的,比如说你声明了一个数组,想知道这个数组的长度则用到了 length 这个属性.</li><li>java 中的 <code>length()</code> 方法是针对字符串说的,如果想看这个字符串的长度则用到 <code>length()</code> 这个方法.</li><li>java 中的 <code>size()</code> 方法是针对泛型集合说的,如果想看这个泛型有多少个元素,就调用此方法来查看!</li></ul><h4 id="hugeCapacity-方法。"><a href="#hugeCapacity-方法。" class="headerlink" title="hugeCapacity() 方法。"></a><code>hugeCapacity()</code> 方法。</h4><p>从上面 <code>grow()</code> 方法源码我们知道： 如果新容量大于 MAX_ARRAY_SIZE,进入(执行) <code>hugeCapacity()</code> 方法来比较 minCapacity 和 MAX_ARRAY_SIZE，如果minCapacity大于最大容量，则新容量则为<code>Integer.MAX_VALUE</code>，否则，新容量大小则为 MAX_ARRAY_SIZE 即为 <code>Integer.MAX_VALUE - 8</code>。 </p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="type">int</span> <span class="title function_">hugeCapacity</span><span class="params">(<span class="type">int</span> minCapacity)</span> &#123;</span><br><span class="line"><span class="keyword">if</span> (minCapacity &lt; <span class="number">0</span>) <span class="comment">// overflow</span></span><br><span class="line"><span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">OutOfMemoryError</span>();</span><br><span class="line"><span class="comment">//对minCapacity和MAX_ARRAY_SIZE进行比较</span></span><br><span class="line"><span class="comment">//若minCapacity大，将Integer.MAX_VALUE作为新数组的大小</span></span><br><span class="line"><span class="comment">//若MAX_ARRAY_SIZE大，将MAX_ARRAY_SIZE作为新数组的大小</span></span><br><span class="line"><span class="comment">//MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;</span></span><br><span class="line"><span class="keyword">return</span> (minCapacity &gt; MAX_ARRAY_SIZE) ?</span><br><span class="line">Integer.MAX_VALUE :</span><br><span class="line">MAX_ARRAY_SIZE;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="System-arraycopy-和-Arrays-copyOf-方法"><a href="#System-arraycopy-和-Arrays-copyOf-方法" class="headerlink" title="System.arraycopy() 和 Arrays.copyOf()方法"></a><code>System.arraycopy()</code> 和 <code>Arrays.copyOf()</code>方法</h3><p>阅读源码的话，我们就会发现 ArrayList 中大量调用了这两个方法。比如：我们上面讲的扩容操作以及<code>add(int index, E element)</code>、<code>toArray()</code> 等方法中都用到了该方法！</p><h4 id="System-arraycopy-方法"><a href="#System-arraycopy-方法" class="headerlink" title="System.arraycopy() 方法"></a><code>System.arraycopy()</code> 方法</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><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="comment">/**</span></span><br><span class="line"><span class="comment"> * 在此列表中的指定位置插入指定的元素。 </span></span><br><span class="line"><span class="comment"> * 先调用 rangeCheckForAdd 对index进行界限检查；然后调用 ensureCapacityInternal 方法保证capacity足够大；</span></span><br><span class="line"><span class="comment"> * 再将从index开始之后的所有成员后移一个位置；将element插入index位置；最后size加1。</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">(<span class="type">int</span> index, E element)</span> &#123;</span><br><span class="line">rangeCheckForAdd(index);</span><br><span class="line"></span><br><span class="line">ensureCapacityInternal(size + <span class="number">1</span>);  <span class="comment">// Increments modCount!!</span></span><br><span class="line"><span class="comment">//arraycopy()方法实现数组自己复制自己</span></span><br><span class="line"><span class="comment">//elementData:源数组;index:源数组中的起始位置;elementData：目标数组；index + 1：目标数组中的起始位置； size - index：要复制的数组元素的数量；</span></span><br><span class="line">System.arraycopy(elementData, index, elementData, index + <span class="number">1</span>, size - index);</span><br><span class="line">elementData[index] = element;</span><br><span class="line">size++;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>我们写一个简单的方法测试以下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ArraycopyTest</span> &#123;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line"><span class="comment">// TODO Auto-generated method stub</span></span><br><span class="line"><span class="type">int</span>[] a = <span class="keyword">new</span> <span class="title class_">int</span>[<span class="number">10</span>];</span><br><span class="line">a[<span class="number">0</span>] = <span class="number">0</span>;</span><br><span class="line">a[<span class="number">1</span>] = <span class="number">1</span>;</span><br><span class="line">a[<span class="number">2</span>] = <span class="number">2</span>;</span><br><span class="line">a[<span class="number">3</span>] = <span class="number">3</span>;</span><br><span class="line">System.arraycopy(a, <span class="number">2</span>, a, <span class="number">3</span>, <span class="number">3</span>);</span><br><span class="line">a[<span class="number">2</span>]=<span class="number">99</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; a.length; i++) &#123;</span><br><span class="line">System.out.println(a[i]);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>结果：</p><figure class="highlight plaintext"><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">0 1 99 2 3 0 0 0 0 0 </span><br><span class="line"></span><br></pre></td></tr></table></figure><h4 id="Arrays-copyOf-方法"><a href="#Arrays-copyOf-方法" class="headerlink" title="Arrays.copyOf()方法"></a><code>Arrays.copyOf()</code>方法</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 以正确的顺序返回一个包含此列表中所有元素的数组（从第一个到最后一个元素）; 返回的数组的运行时类型是指定数组的运行时类型。 </span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> Object[] toArray() &#123;</span><br><span class="line"><span class="comment">//elementData：要复制的数组；size：要复制的长度</span></span><br><span class="line"><span class="keyword">return</span> Arrays.copyOf(elementData, size);</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>个人觉得使用 <code>Arrays.copyOf()</code>方法主要是为了给原有数组扩容，测试代码如下：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ArrayscopyOfTest</span> &#123;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line"><span class="type">int</span>[] a = <span class="keyword">new</span> <span class="title class_">int</span>[<span class="number">3</span>];</span><br><span class="line">a[<span class="number">0</span>] = <span class="number">0</span>;</span><br><span class="line">a[<span class="number">1</span>] = <span class="number">1</span>;</span><br><span class="line">a[<span class="number">2</span>] = <span class="number">2</span>;</span><br><span class="line"><span class="type">int</span>[] b = Arrays.copyOf(a, <span class="number">10</span>);</span><br><span class="line">System.out.println(<span class="string">&quot;b.length&quot;</span>+b.length);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>结果：</p><figure class="highlight plaintext"><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">10</span><br><span class="line"></span><br></pre></td></tr></table></figure><h4 id="两者联系和区别"><a href="#两者联系和区别" class="headerlink" title="两者联系和区别"></a>两者联系和区别</h4><p><strong>联系：</strong> </p><p>看两者源代码可以发现 copyOf() 内部实际调用了 <code>System.arraycopy()</code> 方法 </p><p><strong>区别：</strong></p><p><code>arraycopy()</code> 需要目标数组，将原数组拷贝到你自己定义的数组里或者原数组，而且可以选择拷贝的起点和长度以及放入新数组中的位置 <code>copyOf()</code> 是系统自动在内部新建一个数组，并返回该数组。</p><h3 id="ensureCapacity-方法"><a href="#ensureCapacity-方法" class="headerlink" title="ensureCapacity()方法"></a><code>ensureCapacity()</code>方法</h3><p>ArrayList 源码中有一个 <code>ensureCapacity</code> 方法不知道大家注意到没有，这个方法 ArrayList 内部没有被调用过，所以很显然是提供给用户调用的，那么这个方法有什么作用呢？</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="comment">/**</span></span><br><span class="line"><span class="comment"> * 如有必要，增加此 ArrayList 实例的容量，以确保它至少可以容纳由minimum capacity参数指定的元素数。</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span>   minCapacity   所需的最小容量</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">ensureCapacity</span><span class="params">(<span class="type">int</span> minCapacity)</span> &#123;</span><br><span class="line"><span class="type">int</span> <span class="variable">minExpand</span> <span class="operator">=</span> (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)</span><br><span class="line"><span class="comment">// any size if not default element table</span></span><br><span class="line">? <span class="number">0</span></span><br><span class="line"><span class="comment">// larger than default for default empty table. It&#x27;s already</span></span><br><span class="line"><span class="comment">// supposed to be at default size.</span></span><br><span class="line">: DEFAULT_CAPACITY;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (minCapacity &gt; minExpand) &#123;</span><br><span class="line">ensureExplicitCapacity(minCapacity);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><strong>最好在 add 大量元素之前用 <code>ensureCapacity</code> 方法，以减少增量重新分配的次数</strong></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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">EnsureCapacityTest</span> &#123;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">ArrayList&lt;Object&gt; list = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;Object&gt;();</span><br><span class="line"><span class="keyword">final</span> <span class="type">int</span> <span class="variable">N</span> <span class="operator">=</span> <span class="number">10000000</span>;</span><br><span class="line"><span class="type">long</span> <span class="variable">startTime</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">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; N; i++) &#123;</span><br><span class="line">list.add(i);</span><br><span class="line">&#125;</span><br><span class="line"><span class="type">long</span> <span class="variable">endTime</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line">System.out.println(<span class="string">&quot;使用ensureCapacity方法前：&quot;</span>+(endTime - startTime));</span><br><span class="line"></span><br><span class="line">list = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;Object&gt;();</span><br><span class="line"><span class="type">long</span> <span class="variable">startTime1</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line">list.ensureCapacity(N);</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; N; i++) &#123;</span><br><span class="line">list.add(i);</span><br><span class="line">&#125;</span><br><span class="line"><span class="type">long</span> <span class="variable">endTime1</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line">System.out.println(<span class="string">&quot;使用ensureCapacity方法后：&quot;</span>+(endTime1 - startTime1));</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>运行结果：</p><figure class="highlight 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">使用ensureCapacity方法前：4637</span><br><span class="line">使用ensureCapacity方法后：241</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>通过运行结果，我们可以很明显的看出向 ArrayList 添加大量元素之前最好先使用<code>ensureCapacity</code> 方法，以减少增量重新分配的次数</p><h2 id="HashMap-和-Hashtable-的区别"><a href="#HashMap-和-Hashtable-的区别" class="headerlink" title="HashMap 和 Hashtable 的区别"></a>HashMap 和 Hashtable 的区别</h2><ol><li><strong>线程是否安全：</strong> HashMap 是非线程安全的，HashTable 是线程安全的；HashTable 内部的方法基本都经过<code>synchronized</code> 修饰。（如果你要保证线程安全的话就使用 ConcurrentHashMap 吧！）；</li><li><strong>效率：</strong> 因为线程安全的问题，HashMap 要比 HashTable 效率高一点。另外，HashTable 基本被淘汰，不要在代码中使用它；</li><li><strong>对Null key 和Null value的支持：</strong> HashMap 中，null 可以作为键，这样的键只有一个，可以有一个或多个键所对应的值为 null。。但是在 HashTable 中 put 进的键值只要有一个 null，直接抛出 NullPointerException。</li><li><strong>初始容量大小和每次扩充容量大小的不同 ：</strong> ①创建时如果不指定容量初始值，Hashtable 默认的初始大小为11，之后每次扩充，容量变为原来的2n+1。HashMap 默认的初始化大小为16。之后每次扩充，容量变为原来的2倍。②创建时如果给定了容量初始值，那么 Hashtable 会直接使用你给定的大小，而 HashMap 会将其扩充为2的幂次方大小（HashMap 中的<code>tableSizeFor()</code>方法保证，下面给出了源代码）。也就是说 HashMap 总是使用2的幂作为哈希表的大小,后面会介绍到为什么是2的幂次方。</li><li><strong>底层数据结构：</strong> JDK1.8 以后的 HashMap 在解决哈希冲突时有了较大的变化，当链表长度大于阈值（默认为8）时，将链表转化为红黑树，以减少搜索时间。Hashtable 没有这样的机制。</li></ol><p><strong>HashMap 中带有初始容量的构造函数：</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="title function_">HashMap</span><span class="params">(<span class="type">int</span> initialCapacity, <span class="type">float</span> loadFactor)</span> &#123;</span><br><span class="line"><span class="keyword">if</span> (initialCapacity &lt; <span class="number">0</span>)</span><br><span class="line"><span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(<span class="string">&quot;Illegal initial capacity: &quot;</span> +</span><br><span class="line">   initialCapacity);</span><br><span class="line"><span class="keyword">if</span> (initialCapacity &gt; MAXIMUM_CAPACITY)</span><br><span class="line">initialCapacity = MAXIMUM_CAPACITY;</span><br><span class="line"><span class="keyword">if</span> (loadFactor &lt;= <span class="number">0</span> || Float.isNaN(loadFactor))</span><br><span class="line"><span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(<span class="string">&quot;Illegal load factor: &quot;</span> +</span><br><span class="line">   loadFactor);</span><br><span class="line"><span class="built_in">this</span>.loadFactor = loadFactor;</span><br><span class="line"><span class="built_in">this</span>.threshold = tableSizeFor(initialCapacity);</span><br><span class="line">&#125;</span><br><span class="line"> <span class="keyword">public</span> <span class="title function_">HashMap</span><span class="params">(<span class="type">int</span> initialCapacity)</span> &#123;</span><br><span class="line"><span class="built_in">this</span>(initialCapacity, DEFAULT_LOAD_FACTOR);</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>下面这个方法保证了 HashMap 总是使用2的幂作为哈希表的大小。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Returns a power of two size for the given target capacity.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="title function_">tableSizeFor</span><span class="params">(<span class="type">int</span> cap)</span> &#123;</span><br><span class="line"><span class="type">int</span> <span class="variable">n</span> <span class="operator">=</span> cap - <span class="number">1</span>;</span><br><span class="line">n |= n &gt;&gt;&gt; <span class="number">1</span>;</span><br><span class="line">n |= n &gt;&gt;&gt; <span class="number">2</span>;</span><br><span class="line">n |= n &gt;&gt;&gt; <span class="number">4</span>;</span><br><span class="line">n |= n &gt;&gt;&gt; <span class="number">8</span>;</span><br><span class="line">n |= n &gt;&gt;&gt; <span class="number">16</span>;</span><br><span class="line"><span class="keyword">return</span> (n &lt; <span class="number">0</span>) ? <span class="number">1</span> : (n &gt;= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + <span class="number">1</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="HashMap-和-HashSet区别"><a href="#HashMap-和-HashSet区别" class="headerlink" title="HashMap 和 HashSet区别"></a>HashMap 和 HashSet区别</h2><p>如果你看过 <code>HashSet</code> 源码的话就应该知道：HashSet 底层就是基于 HashMap 实现的。（HashSet 的源码非常非常少，因为除了 <code>clone() </code>、<code>writeObject()</code>、<code>readObject()</code>是 HashSet 自己不得不实现之外，其他方法都是直接调用 HashMap 中的方法。</p><table><thead><tr><th align="center">HashMap</th><th align="center">HashSet</th></tr></thead><tbody><tr><td align="center">实现了Map接口</td><td align="center">实现Set接口</td></tr><tr><td align="center">存储键值对</td><td align="center">仅存储对象</td></tr><tr><td align="center">调用 <code>put（）</code>向map中添加元素</td><td align="center">调用 <code>add（）</code>方法向Set中添加元素</td></tr><tr><td align="center">HashMap使用键（Key）计算Hashcode</td><td align="center">HashSet使用成员对象来计算hashcode值，对于两个对象来说hashcode可能相同，所以equals()方法用来判断对象的相等性，</td></tr></tbody></table><h2 id="HashSet如何检查重复"><a href="#HashSet如何检查重复" class="headerlink" title="HashSet如何检查重复"></a>HashSet如何检查重复</h2><p>当你把对象加入<code>HashSet</code>时，HashSet会先计算对象的<code>hashcode</code>值来判断对象加入的位置，同时也会与其他加入的对象的hashcode值作比较，如果没有相符的hashcode，HashSet会假设对象没有重复出现。但是如果发现有相同hashcode值的对象，这时会调用<code>equals（）</code>方法来检查hashcode相等的对象是否真的相同。如果两者相同，HashSet就不会让加入操作成功。（摘自《Head fist java》第二版）</p><p><strong>hashCode()与equals()的相关规定：</strong></p><ol><li>如果两个对象相等，则hashcode一定也是相同的</li><li>两个对象相等,对两个equals方法返回true</li><li>两个对象有相同的hashcode值，它们也不一定是相等的</li><li>综上，equals方法被覆盖过，则hashCode方法也必须被覆盖</li><li>hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode()，则该class的两个对象无论如何都不会相等（即使这两个对象指向相同的数据）。</li></ol><p><strong>&#x3D;&#x3D;与equals的区别</strong></p><ol><li>&#x3D;&#x3D;是判断两个变量或实例是不是指向同一个内存空间 equals是判断两个变量或实例所指向的内存空间的值是不是相同</li><li>&#x3D;&#x3D;是指对内存地址进行比较 equals()是对字符串的内容进行比较</li><li>&#x3D;&#x3D;指引用是否相同 equals()指的是值是否相同</li></ol><h2 id="HashMap的底层实现"><a href="#HashMap的底层实现" class="headerlink" title="HashMap的底层实现"></a>HashMap的底层实现</h2><h3 id="JDK1-8之前"><a href="#JDK1-8之前" class="headerlink" title="JDK1.8之前"></a>JDK1.8之前</h3><p>JDK1.8 之前 <code>HashMap</code> 底层是 <strong>数组和链表</strong> 结合在一起使用也就是 <strong>链表散列</strong>。<strong>HashMap 通过 key 的 hashCode 经过扰动函数处理过后得到 hash 值，然后通过 (n - 1) &amp; hash 判断当前元素存放的位置（这里的 n 指的是数组的长度），如果当前位置存在元素的话，就判断该元素与要存入的元素的 hash 值以及 key 是否相同，如果相同的话，直接覆盖，不相同就通过拉链法解决冲突。</strong></p><p><strong>所谓扰动函数指的就是 HashMap 的 hash 方法。使用 hash 方法也就是扰动函数是为了防止一些实现比较差的 hashCode() 方法 换句话说使用扰动函数之后可以减少碰撞。</strong></p><p><strong>JDK 1.8 HashMap 的 hash 方法源码:</strong></p><p>JDK 1.8 的 hash方法 相比于 JDK 1.7 hash 方法更加简化，但是原理不变。</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">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="title function_">hash</span><span class="params">(Object key)</span> &#123;</span><br><span class="line">  <span class="type">int</span> h;</span><br><span class="line">  <span class="comment">// key.hashCode()：返回散列值也就是hashcode</span></span><br><span class="line">  <span class="comment">// ^ ：按位异或</span></span><br><span class="line">  <span class="comment">// &gt;&gt;&gt;:无符号右移，忽略符号位，空位都以0补齐</span></span><br><span class="line">  <span class="keyword">return</span> (key == <span class="literal">null</span>) ? <span class="number">0</span> : (h = key.hashCode()) ^ (h &gt;&gt;&gt; <span class="number">16</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>对比一下 JDK1.7的 HashMap 的 hash 方法源码.</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="type">int</span> <span class="title function_">hash</span><span class="params">(<span class="type">int</span> h)</span> &#123;</span><br><span class="line">    <span class="comment">// This function ensures that hashCodes that differ only by</span></span><br><span class="line">    <span class="comment">// constant multiples at each bit position have a bounded</span></span><br><span class="line">    <span class="comment">// number of collisions (approximately 8 at default load factor).</span></span><br><span class="line"></span><br><span class="line">    h ^= (h &gt;&gt;&gt; <span class="number">20</span>) ^ (h &gt;&gt;&gt; <span class="number">12</span>);</span><br><span class="line">    <span class="keyword">return</span> h ^ (h &gt;&gt;&gt; <span class="number">7</span>) ^ (h &gt;&gt;&gt; <span class="number">4</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>相比于 JDK1.8 的 hash 方法 ，JDK 1.7 的 hash 方法的性能会稍差一点点，因为毕竟扰动了 4 次。</p><p>所谓 <strong>“拉链法”</strong> 就是：将链表和数组相结合。也就是说创建一个链表数组，数组中每一格就是一个链表。若遇到哈希冲突，则将冲突的值加到链表中即可。</p><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/27/15190f80-c87a-11e9-afb6-c9e9c0891586.jpg" alt="jdk1.8之前的内部结构HashMap.jpg"></p><h3 id="JDK1-8之后"><a href="#JDK1-8之后" class="headerlink" title="JDK1.8之后"></a>JDK1.8之后</h3><p>相比于之前的版本， JDK1.8之后在解决哈希冲突时有了较大的变化，当链表长度大于阈值（默认为8）时，将链表转化为红黑树，以减少搜索时间。</p><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/27/285efd70-c87a-11e9-afb6-c9e9c0891586.jpg" alt="JDK1.8之后的HashMap底层数据结构.jpg"></p><blockquote><p>TreeMap、TreeSet以及JDK1.8之后的HashMap底层都用到了红黑树。红黑树就是为了解决二叉查找树的缺陷，因为二叉查找树在某些情况下会退化成一个线性结构。</p></blockquote><p><strong>推荐阅读：</strong></p><ul><li><a href="https://zhuanlan.zhihu.com/p/21673805">《Java 8系列之重新认识HashMap》</a></li></ul><h2 id="HashMap-的长度为什么是2的幂次方"><a href="#HashMap-的长度为什么是2的幂次方" class="headerlink" title="HashMap 的长度为什么是2的幂次方"></a>HashMap 的长度为什么是2的幂次方</h2><p>为了能让 HashMap 存取高效，尽量较少碰撞，也就是要尽量把数据分配均匀。我们上面也讲到了过了，Hash 值的范围值-2147483648到2147483647，前后加起来大概40亿的映射空间，只要哈希函数映射得比较均匀松散，一般应用是很难出现碰撞的。但问题是一个40亿长度的数组，内存是放不下的。所以这个散列值是不能直接拿来用的。用之前还要先做对数组的长度取模运算，得到的余数才能用来要存放的位置也就是对应的数组下标。这个数组下标的计算方法是“ <code>(n - 1) &amp; hash</code>”。（n代表数组长度）。这也就解释了 HashMap 的长度为什么是2的幂次方。</p><p><strong>这个算法应该如何设计呢？</strong></p><p>我们首先可能会想到采用%取余的操作来实现。但是，重点来了：<strong>“取余(%)操作中如果除数是2的幂次则等价于与其除数减一的与(&amp;)操作（也就是说 hash%length&#x3D;&#x3D;hash&amp;(length-1)的前提是 length 是2的 n 次方；）。”</strong> 并且 <strong>采用二进制位操作 &amp;，相对于%能够提高运算效率，这就解释了 HashMap 的长度为什么是2的幂次方。</strong></p><h2 id="HashMap-多线程操作导致死循环问题"><a href="#HashMap-多线程操作导致死循环问题" class="headerlink" title="HashMap 多线程操作导致死循环问题"></a>HashMap 多线程操作导致死循环问题</h2><p>主要原因在于 并发下的Rehash会造成元素之间会形成一个循环链表。不过，jdk 1.8 后解决了这个问题，但是还是不建议在多线程下使用 HashMap,因为多线程下使用 HashMap 还是会存在其他问题比如数据丢失。并发环境下推荐使用 ConcurrentHashMap 。</p><p><a href="https://coolshell.cn/articles/9606.html">详情请查看</a></p><h2 id="ConcurrentHashMap-和-Hashtable-的区别"><a href="#ConcurrentHashMap-和-Hashtable-的区别" class="headerlink" title="ConcurrentHashMap 和 Hashtable 的区别"></a>ConcurrentHashMap 和 Hashtable 的区别</h2><p>ConcurrentHashMap 和 Hashtable 的区别主要体现在实现线程安全的方式上不同。</p><ul><li><strong>底层数据结构：</strong> JDK1.7的 ConcurrentHashMap 底层采用 <strong>分段的数组+链表</strong> 实现，JDK1.8 采用的数据结构跟HashMap1.8的结构一样，数组+链表&#x2F;红黑二叉树。Hashtable 和 JDK1.8 之前的 HashMap 的底层数据结构类似都是采用 <strong>数组+链表</strong> 的形式，数组是 HashMap 的主体，链表则是主要为了解决哈希冲突而存在的；</li><li><strong>实现线程安全的方式（重要）：</strong> ① <strong>在JDK1.7的时候，ConcurrentHashMap（分段锁）</strong> 对整个桶数组进行了分割分段(Segment)，每一把锁只锁容器其中一部分数据，多线程访问容器里不同数据段的数据，就不会存在锁竞争，提高并发访问率。 <strong>到了 JDK1.8 的时候已经摒弃了Segment的概念，而是直接用 Node 数组+链表+红黑树的数据结构来实现，并发控制使用 synchronized 和 CAS 来操作。（JDK1.6以后 对 synchronized锁做了很多优化）</strong> 整个看起来就像是优化过且线程安全的 HashMap，虽然在JDK1.8中还能看到 Segment 的数据结构，但是已经简化了属性，只是为了兼容旧版本；② <strong>Hashtable(同一把锁)</strong> :使用 synchronized 来保证线程安全，效率非常低下。当一个线程访问同步方法时，其他线程也访问同步方法，可能会进入阻塞或轮询状态，如使用 put 添加元素，另一个线程不能使用 put 添加元素，也不能使用 get，竞争会越来越激烈效率越低。</li></ul><p><strong>两者的对比图：</strong></p><p><strong>HashTable:</strong></p><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/27/41d71210-c87a-11e9-afb6-c9e9c0891586.png" alt="HashTable全表锁.png"></p><p><strong>JDK1.7的ConcurrentHashMap：</strong></p><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/27/517099d0-c87a-11e9-afb6-c9e9c0891586.jpg" alt="ConcurrentHashMap分段锁.jpg"></p><p><strong>JDK1.8的ConcurrentHashMap（TreeBin: 红黑二叉树节点 Node: 链表节点）：</strong></p><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/27/5e87e380-c87a-11e9-afb6-c9e9c0891586.jpg" alt="JDK1.8ConcurrentHashMapStructure.jpg"></p><h2 id="ConcurrentHashMap线程安全的具体实现方式-底层具体实现"><a href="#ConcurrentHashMap线程安全的具体实现方式-底层具体实现" class="headerlink" title="ConcurrentHashMap线程安全的具体实现方式&#x2F;底层具体实现"></a>ConcurrentHashMap线程安全的具体实现方式&#x2F;底层具体实现</h2><h3 id="JDK1-7（上面有示意图）"><a href="#JDK1-7（上面有示意图）" class="headerlink" title="JDK1.7（上面有示意图）"></a>JDK1.7（上面有示意图）</h3><p>首先将数据分为一段一段的存储，然后给每一段数据配一把锁，当一个线程占用锁访问其中一个段数据时，其他段的数据也能被其他线程访问。</p><p><strong>ConcurrentHashMap 是由 Segment 数组结构和 HashEntry 数组结构组成</strong>。</p><p>Segment 实现了 ReentrantLock,所以 Segment 是一种可重入锁，扮演锁的角色。HashEntry 用于存储键值对数据。</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">static</span> <span class="keyword">class</span> <span class="title class_">Segment</span>&lt;K,V&gt; <span class="keyword">extends</span> <span class="title class_">ReentrantLock</span> <span class="keyword">implements</span> <span class="title class_">Serializable</span> &#123;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>一个 ConcurrentHashMap 里包含一个 Segment 数组。Segment 的结构和HashMap类似，是一种数组和链表结构，一个 Segment 包含一个 HashEntry 数组，每个 HashEntry 是一个链表结构的元素，每个 Segment 守护着一个HashEntry数组里的元素，当对 HashEntry 数组的数据进行修改时，必须首先获得对应的 Segment的锁。</p><h3 id="JDK1-8-（上面有示意图）"><a href="#JDK1-8-（上面有示意图）" class="headerlink" title="JDK1.8 （上面有示意图）"></a>JDK1.8 （上面有示意图）</h3><p>ConcurrentHashMap取消了Segment分段锁，采用CAS和synchronized来保证并发安全。数据结构跟HashMap1.8的结构类似，数组+链表&#x2F;红黑二叉树。Java 8在链表长度超过一定阈值（8）时将链表（寻址时间复杂度为O(N)）转换为红黑树（寻址时间复杂度为O(log(N))）</p><p>synchronized只锁定当前链表或红黑二叉树的首节点，这样只要hash不冲突，就不会产生并发，效率又提升N倍。</p><h2 id="comparable-和-Comparator的区别"><a href="#comparable-和-Comparator的区别" class="headerlink" title="comparable 和 Comparator的区别"></a>comparable 和 Comparator的区别</h2><ul><li>comparable接口实际上是出自java.lang包 它有一个 <code>compareTo(Object obj)</code>方法用来排序</li><li>comparator接口实际上是出自 java.util 包它有一个<code>compare(Object obj1, Object obj2)</code>方法用来排序</li></ul><p>一般我们需要对一个集合使用自定义排序时，我们就要重写<code>compareTo()</code>方法或<code>compare()</code>方法，当我们需要对某一个集合实现两种排序方式，比如一个song对象中的歌名和歌手名分别采用一种排序方法的话，我们可以重写<code>compareTo()</code>方法和使用自制的Comparator方法或者以两个Comparator来实现歌名排序和歌星名排序，第二种代表我们只能使用两个参数版的 <code>Collections.sort()</code>.</p><h3 id="Comparator定制排序"><a href="#Comparator定制排序" class="headerlink" title="Comparator定制排序"></a>Comparator定制排序</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line">ArrayList&lt;Integer&gt; arrayList = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;Integer&gt;();</span><br><span class="line">arrayList.add(-<span class="number">1</span>);</span><br><span class="line">arrayList.add(<span class="number">3</span>);</span><br><span class="line">arrayList.add(<span class="number">3</span>);</span><br><span class="line">arrayList.add(-<span class="number">5</span>);</span><br><span class="line">arrayList.add(<span class="number">7</span>);</span><br><span class="line">arrayList.add(<span class="number">4</span>);</span><br><span class="line">arrayList.add(-<span class="number">9</span>);</span><br><span class="line">arrayList.add(-<span class="number">7</span>);</span><br><span class="line">System.out.println(<span class="string">&quot;原始数组:&quot;</span>);</span><br><span class="line">System.out.println(arrayList);</span><br><span class="line"><span class="comment">// void reverse(List list)：反转</span></span><br><span class="line">Collections.reverse(arrayList);</span><br><span class="line">System.out.println(<span class="string">&quot;Collections.reverse(arrayList):&quot;</span>);</span><br><span class="line">System.out.println(arrayList);</span><br><span class="line"></span><br><span class="line"><span class="comment">// void sort(List list),按自然排序的升序排序</span></span><br><span class="line">Collections.sort(arrayList);</span><br><span class="line">System.out.println(<span class="string">&quot;Collections.sort(arrayList):&quot;</span>);</span><br><span class="line">System.out.println(arrayList);</span><br><span class="line"><span class="comment">// 定制排序的用法</span></span><br><span class="line">Collections.sort(arrayList, <span class="keyword">new</span> <span class="title class_">Comparator</span>&lt;Integer&gt;() &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">compare</span><span class="params">(Integer o1, Integer o2)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> o2.compareTo(o1);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br><span class="line">System.out.println(<span class="string">&quot;定制排序后：&quot;</span>);</span><br><span class="line">System.out.println(arrayList);</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>Output:</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></pre></td><td class="code"><pre><span class="line">原始数组:</span><br><span class="line">[-1, 3, 3, -5, 7, 4, -9, -7]</span><br><span class="line">Collections.reverse(arrayList):</span><br><span class="line">[-7, -9, 4, 7, -5, 3, 3, -1]</span><br><span class="line">Collections.sort(arrayList):</span><br><span class="line">[-9, -7, -5, -1, 3, 3, 4, 7]</span><br><span class="line">定制排序后：</span><br><span class="line">[7, 4, 3, 3, -1, -5, -7, -9]</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="重写compareTo方法实现按年龄来排序"><a href="#重写compareTo方法实现按年龄来排序" class="headerlink" title="重写compareTo方法实现按年龄来排序"></a>重写compareTo方法实现按年龄来排序</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// person对象没有实现Comparable接口，所以必须实现，这样才不会出错，才可以使treemap中的数据按顺序排列</span></span><br><span class="line"><span class="comment">// 前面一个例子的String类已经默认实现了Comparable接口，详细可以查看String类的API文档，另外其他</span></span><br><span class="line"><span class="comment">// 像Integer类等都已经实现了Comparable接口，所以不需要另外实现了</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span>  <span class="keyword">class</span> <span class="title class_">Person</span> <span class="keyword">implements</span> <span class="title class_">Comparable</span>&lt;Person&gt; &#123;</span><br><span class="line">    <span class="keyword">private</span> String name;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> age;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">Person</span><span class="params">(String name, <span class="type">int</span> age)</span> &#123;</span><br><span class="line">        <span class="built_in">super</span>();</span><br><span class="line">        <span class="built_in">this</span>.name = name;</span><br><span class="line">        <span class="built_in">this</span>.age = age;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">getName</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> name;</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_">setName</span><span class="params">(String name)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.name = name;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">getAge</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> age;</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_">setAge</span><span class="params">(<span class="type">int</span> age)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.age = age;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * TODO重写compareTo方法实现按年龄来排序</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">compareTo</span><span class="params">(Person o)</span> &#123;</span><br><span class="line">        <span class="comment">// TODO Auto-generated method stub</span></span><br><span class="line">        <span class="keyword">if</span> (<span class="built_in">this</span>.age &gt; o.getAge()) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (<span class="built_in">this</span>.age &lt; o.getAge()) &#123;</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> age;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">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">    TreeMap&lt;Person, String&gt; pdata = <span class="keyword">new</span> <span class="title class_">TreeMap</span>&lt;Person, String&gt;();</span><br><span class="line">    pdata.put(<span class="keyword">new</span> <span class="title class_">Person</span>(<span class="string">&quot;张三&quot;</span>, <span class="number">30</span>), <span class="string">&quot;zhangsan&quot;</span>);</span><br><span class="line">    pdata.put(<span class="keyword">new</span> <span class="title class_">Person</span>(<span class="string">&quot;李四&quot;</span>, <span class="number">20</span>), <span class="string">&quot;lisi&quot;</span>);</span><br><span class="line">    pdata.put(<span class="keyword">new</span> <span class="title class_">Person</span>(<span class="string">&quot;王五&quot;</span>, <span class="number">10</span>), <span class="string">&quot;wangwu&quot;</span>);</span><br><span class="line">    pdata.put(<span class="keyword">new</span> <span class="title class_">Person</span>(<span class="string">&quot;小红&quot;</span>, <span class="number">5</span>), <span class="string">&quot;xiaohong&quot;</span>);</span><br><span class="line">    <span class="comment">// 得到key的值的同时得到key所对应的值</span></span><br><span class="line">    Set&lt;Person&gt; keys = pdata.keySet();</span><br><span class="line">    <span class="keyword">for</span> (Person key : keys) &#123;</span><br><span class="line">        System.out.println(key.getAge() + <span class="string">&quot;-&quot;</span> + key.getName());</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>Output：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">5-小红</span><br><span class="line">10-王五</span><br><span class="line">20-李四</span><br><span class="line">30-张三</span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="集合框架底层数据结构总结"><a href="#集合框架底层数据结构总结" class="headerlink" title="集合框架底层数据结构总结"></a>集合框架底层数据结构总结</h2><h3 id="Collection"><a href="#Collection" class="headerlink" title="Collection"></a>Collection</h3><h4 id="List"><a href="#List" class="headerlink" title="List"></a>List</h4><ul><li><strong>Arraylist：</strong> Object数组</li><li><strong>Vector：</strong> Object数组</li><li><strong>LinkedList：</strong> 双向链表(JDK1.6之前为循环链表，JDK1.7取消了循环)</li></ul><h4 id="Set"><a href="#Set" class="headerlink" title="Set"></a>Set</h4><ul><li><strong>HashSet（无序，唯一）:</strong> 基于 HashMap 实现的，底层采用 HashMap 来保存元素</li><li><strong>LinkedHashSet：</strong> LinkedHashSet 继承于 HashSet，并且其内部是通过 LinkedHashMap 来实现的。有点类似于我们之前说的LinkedHashMap 其内部是基于 HashMap 实现一样，不过还是有一点点区别的</li><li><strong>TreeSet（有序，唯一）：</strong> 红黑树(自平衡的排序二叉树)</li></ul><h3 id="Map"><a href="#Map" class="headerlink" title="Map"></a>Map</h3><ul><li><strong>HashMap：</strong> JDK1.8之前HashMap由数组+链表组成的，数组是HashMap的主体，链表则是主要为了解决哈希冲突而存在的（“拉链法”解决冲突）。JDK1.8以后在解决哈希冲突时有了较大的变化，当链表长度大于阈值（默认为8）时，将链表转化为红黑树，以减少搜索时间</li><li><strong>LinkedHashMap：</strong> LinkedHashMap 继承自 HashMap，所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外，LinkedHashMap 在上面结构的基础上，增加了一条双向链表，使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作，实现了访问顺序相关逻辑。详细可以查看：<a href="https://www.imooc.com/article/22931">《LinkedHashMap 源码详细分析（JDK1.8）》</a></li><li><strong>Hashtable：</strong> 数组+链表组成的，数组是 HashMap 的主体，链表则是主要为了解决哈希冲突而存在的</li><li><strong>TreeMap：</strong> 红黑树（自平衡的排序二叉树）</li></ul><h2 id="如何选用集合"><a href="#如何选用集合" class="headerlink" title="如何选用集合?"></a>如何选用集合?</h2><p>主要根据集合的特点来选用，比如我们需要根据键值获取到元素值时就选用Map接口下的集合，需要排序时选择TreeMap,不需要排序时就选择HashMap,需要保证线程安全就选用ConcurrentHashMap.当我们只需要存放元素值时，就选择实现Collection接口的集合，需要保证元素唯一时选择实现Set接口的集合比如TreeSet或HashSet，不需要就选择实现List接口的比如ArrayList或LinkedList，然后再根据实现这些接口的集合的特点来选用。</p><h2 id="ArrayList源码"><a href="#ArrayList源码" class="headerlink" title="ArrayList源码"></a>ArrayList源码</h2><h3 id="ArrayList简介"><a href="#ArrayList简介" class="headerlink" title="ArrayList简介"></a>ArrayList简介</h3><p>ArrayList 的底层是数组队列，相当于动态数组。与 Java 中的数组相比，它的容量能动态增长。在添加大量元素前，应用程序可以使用<code>ensureCapacity</code>操作来增加 ArrayList 实例的容量。这可以减少递增式再分配的数量。<br>它继承于 <strong>AbstractList</strong>，实现了 <strong>List</strong>, <strong>RandomAccess</strong> , <strong>Cloneable</strong> , <strong>java.io.Serializable</strong> 这些接口。</p><p>在我们学数据结构的时候就知道了线性表的顺序存储，插入删除元素的时间复杂度为<strong>O(n)</strong> ,求表长以及增加元素，取第i元素的时间复杂度为 <strong>O(1)</strong></p><ul><li>继承了AbstractList，实现了List。它是一个数组队列，提供了相关的添加、删除、修改、遍历等功能。</li><li>实现了 <strong>RandomAccess接口</strong> ， RandomAccess是一个标志接口，表明实现这个这个接口的 List 集合是支持 <strong>快速随机访问</strong> 的。在 ArrayList 中，我们即可以通过元素的序号快速获取元素对象，这就是快速随机访问。</li><li>实现 <strong>Cloneable 接口</strong>，即覆盖了函数 clone()，<strong>能被克隆</strong>。</li><li>实现 <strong>java.io.Serializable接口</strong> ，这意味着ArrayList <strong>支持序列化</strong> ， <strong>能通过序列化去传输</strong> 。</li><li>与Vector 不同， <strong>ArrayList中的操作不是线程安全的</strong> ！所以，建议在单线程中才使用 ArrayList，而在多线程中可以选择 Vector 或者 CopyOnWriteArrayList。</li></ul><h1 id="并发"><a href="#并发" class="headerlink" title="并发"></a>并发</h1><h2 id="什么是线程和进程"><a href="#什么是线程和进程" class="headerlink" title="什么是线程和进程?"></a>什么是线程和进程?</h2><h3 id="何为进程"><a href="#何为进程" class="headerlink" title="何为进程?"></a>何为进程?</h3><p>进程是程序的一次执行过程，是系统运行程序的基本单位，因此进程是动态的。系统运行一个程序即是一个进程从创建，运行到消亡的过程。<br>在 Java 中，当我们启动 main 函数时其实就是启动了一个 JVM 的进程，而 main 函数所在的线程就是这个进程中的一个线程，也称主线程。<br>如下图所示，在 windows 中通过查看任务管理器的方式，我们就可以清楚看到 window 当前运行的进程（.exe 文件的运行）。</p><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/27/e623b890-c88f-11e9-afb6-c9e9c0891586.png" alt="win10任务管理器.png"></p><h3 id="何为线程"><a href="#何为线程" class="headerlink" title="何为线程?"></a>何为线程?</h3><p>线程与进程相似，但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享进程的<strong>堆</strong>和<strong>方法区</strong>资源，但每个线程有自己的<strong>程序计数器</strong>、<strong>虚拟机栈</strong>和<strong>本地方法栈</strong>，所以系统在产生一个线程，或是在各个线程之间作切换工作时，负担要比进程小得多，也正因为如此，线程也被称为轻量级进程。</p><p>Java 程序天生就是多线程程序，我们可以通过 JMX 来看一下一个普通的 Java 程序有哪些线程，代码如下。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MultiThread</span> &#123;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line"><span class="comment">// 获取 Java 线程管理 MXBean</span></span><br><span class="line"><span class="type">ThreadMXBean</span> <span class="variable">threadMXBean</span> <span class="operator">=</span> ManagementFactory.getThreadMXBean();</span><br><span class="line"><span class="comment">// 不需要获取同步的 monitor 和 synchronizer 信息，仅获取线程和线程堆栈信息</span></span><br><span class="line">ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(<span class="literal">false</span>, <span class="literal">false</span>);</span><br><span class="line"><span class="comment">// 遍历线程信息，仅打印线程 ID 和线程名称信息</span></span><br><span class="line"><span class="keyword">for</span> (ThreadInfo threadInfo : threadInfos) &#123;</span><br><span class="line">System.out.println(<span class="string">&quot;[&quot;</span> + threadInfo.getThreadId() + <span class="string">&quot;] &quot;</span> + threadInfo.getThreadName());</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>上述程序输出如下（输出内容可能不同，不用太纠结下面每个线程的作用，只用知道 main 线程执行 main 方法即可）：</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></pre></td><td class="code"><pre><span class="line">[5] Attach Listener //添加事件</span><br><span class="line">[4] Signal Dispatcher // 分发处理给 JVM 信号的线程</span><br><span class="line">[3] Finalizer //调用对象 finalize 方法的线程</span><br><span class="line">[2] Reference Handler //清除 reference 线程</span><br><span class="line">[1] main //main 线程,程序入口</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>从上面的输出内容可以看出：<strong>一个 Java 程序的运行是 main 线程和多个其他线程同时运行</strong>。</p><h2 id="请简要描述线程与进程的关系-区别及优缺点？"><a href="#请简要描述线程与进程的关系-区别及优缺点？" class="headerlink" title="请简要描述线程与进程的关系,区别及优缺点？"></a>请简要描述线程与进程的关系,区别及优缺点？</h2><p><strong>从 JVM 角度说进程和线程之间的关系</strong></p><h3 id="图解进程和线程的关系"><a href="#图解进程和线程的关系" class="headerlink" title="图解进程和线程的关系"></a>图解进程和线程的关系</h3><p><strong>详细内容请参照JVM相关知识部分内容</strong></p><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/09/05/d6114800-cf79-11e9-a36e-915d01a5fc8b.png" alt="运行时数据区域.png"></p><p>从上图可以看出：一个进程中可以有多个线程，多个线程共享进程的<strong>堆</strong>和<strong>方法区 (JDK1.8 之后的元空间)<strong>资源，但是每个线程有自己的</strong>程序计数器</strong>、<strong>虚拟机栈</strong> 和 <strong>本地方法栈</strong>。</p><p><strong>总结：</strong> 线程 是 进程 划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的，而各线程则不一定，因为同一进程中的线程极有可能会相互影响。线程执行开销小，但不利于资源的管理和保护；而进程正相反。</p><h3 id="程序计数器为什么是私有的"><a href="#程序计数器为什么是私有的" class="headerlink" title="程序计数器为什么是私有的?"></a>程序计数器为什么是私有的?</h3><p>程序计数器主要有下面两个作用：</p><ol><li>字节码解释器通过改变程序计数器来依次读取指令，从而实现代码的流程控制，如：顺序执行、选择、循环、异常处理。</li><li>在多线程的情况下，程序计数器用于记录当前线程执行的位置，从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。</li></ol><p>需要注意的是，如果执行的是 native 方法，那么程序计数器记录的是 undefined 地址，只有执行的是 Java 代码时程序计数器记录的才是下一条指令的地址。</p><p>所以，程序计数器私有主要是为了<strong>线程切换后能恢复到正确的执行位置</strong>。</p><h3 id="虚拟机栈和本地方法栈为什么是私有的"><a href="#虚拟机栈和本地方法栈为什么是私有的" class="headerlink" title="虚拟机栈和本地方法栈为什么是私有的?"></a>虚拟机栈和本地方法栈为什么是私有的?</h3><ul><li><strong>虚拟机栈：</strong> 每个 Java 方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、常量池引用等信息。从方法调用直至执行完成的过程，就对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。</li><li><strong>本地方法栈：</strong> 和虚拟机栈所发挥的作用非常相似，区别是： <strong>虚拟机栈为虚拟机执行 Java 方法 （也就是字节码）服务，而本地方法栈则为虚拟机使用到的 Native 方法服务。</strong> 在 HotSpot 虚拟机中和 Java 虚拟机栈合二为一。</li></ul><p>所以，为了<strong>保证线程中的局部变量不被别的线程访问到</strong>，虚拟机栈和本地方法栈是线程私有的。</p><h3 id="一句话简单了解堆和方法区"><a href="#一句话简单了解堆和方法区" class="headerlink" title="一句话简单了解堆和方法区"></a>一句话简单了解堆和方法区</h3><p>堆和方法区是所有线程共享的资源，其中堆是进程中最大的一块内存，主要用于存放新创建的对象 (所有对象都在这里分配内存)，方法区主要用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。</p><h2 id="说说并发与并行的区别"><a href="#说说并发与并行的区别" class="headerlink" title="说说并发与并行的区别?"></a>说说并发与并行的区别?</h2><ul><li><strong>并发：</strong> 同一时间段，多个任务都在执行 (单位时间内不一定同时执行)；</li><li><strong>并行：</strong> 单位时间内，多个任务同时执行。</li></ul><h2 id="为什么要使用多线程呢"><a href="#为什么要使用多线程呢" class="headerlink" title="为什么要使用多线程呢?"></a>为什么要使用多线程呢?</h2><p>先从总体上来说：</p><ul><li><strong>从计算机底层来说：</strong> 线程可以比作是轻量级的进程，是程序执行的最小单位,线程间的切换和调度的成本远远小于进程。另外，多核 CPU 时代意味着多个线程可以同时运行，这减少了线程上下文切换的开销。</li><li><strong>从当代互联网发展趋势来说：</strong> 现在的系统动不动就要求百万级甚至千万级的并发量，而多线程并发编程正是开发高并发系统的基础，利用好多线程机制可以大大提高系统整体的并发能力以及性能。</li></ul><p>再深入到计算机底层来探讨：</p><ul><li><strong>单核时代：</strong> 在单核时代多线程主要是为了提高 CPU 和 IO 设备的综合利用率。举个例子：当只有一个线程的时候会导致 CPU 计算时，IO 设备空闲；进行 IO 操作时，CPU 空闲。我们可以简单地说这两者的利用率目前都是 50%左右。但是当有两个线程的时候就不一样了，当一个线程执行 CPU 计算时，另外一个线程可以进行 IO 操作，这样两个的利用率就可以在理想情况下达到 100%了。</li><li><strong>多核时代:</strong> 多核时代多线程主要是为了提高 CPU 利用率。举个例子：假如我们要计算一个复杂的任务，我们只用一个线程的话，CPU 只会一个 CPU 核心被利用到，而创建多个线程就可以让多个 CPU 核心被利用到，这样就提高了 CPU 的利用率。</li></ul><h2 id="使用多线程可能带来什么问题"><a href="#使用多线程可能带来什么问题" class="headerlink" title="使用多线程可能带来什么问题?"></a>使用多线程可能带来什么问题?</h2><p>并发编程的目的就是为了能提高程序的执行效率提高程序运行速度，但是并发编程并不总是能提高程序运行速度的，而且并发编程可能会遇到很多问题，比如：内存泄漏、上下文切换、死锁还有受限于硬件和软件的资源闲置问题。</p><h2 id="说说线程的生命周期和状态"><a href="#说说线程的生命周期和状态" class="headerlink" title="说说线程的生命周期和状态?"></a>说说线程的生命周期和状态?</h2><p>Java 线程在运行的生命周期中的指定时刻只可能处于下面 6 种不同状态的其中一个状态<br><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/09/05/b3191d60-cf7d-11e9-a36e-915d01a5fc8b.png" alt="Java 线程的状态.png"></p><p>线程在生命周期中并不是固定处于某一个状态而是随着代码的执行在不同状态之间切换。Java 线程状态变迁如下图所示<br><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/09/05/c28b4700-cf7d-11e9-a36e-915d01a5fc8b.png" alt="Java 线程状态变迁.png"></p><p>由上图可以看出：线程创建之后它将处于 <strong>NEW（新建）</strong> 状态，调用 <code>start()</code> 方法后开始运行，线程这时候处于 <strong>READY（可运行）</strong> 状态。可运行状态的线程获得了 CPU 时间片（timeslice）后就处于 <strong>RUNNING（运行）</strong> 状态。</p><blockquote><p>操作系统隐藏 Java 虚拟机（JVM）中的 RUNNABLE 和 RUNNING 状态，它只能看到 RUNNABLE 状态，所以 Java 系统一般将这两个状态统称为 <strong>RUNNABLE（运行中）</strong> 状态 。</p></blockquote><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/09/05/e6439080-cf7d-11e9-a36e-915d01a5fc8b.png" alt="RUNNABLE-VS-RUNNING.png"></p><p>当线程执行 <code>wait()</code>方法之后，线程进入 <strong>WAITING（等待）</strong> 状态。进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态，而 <strong>TIME_WAITING(超时等待)</strong> 状态相当于在等待状态的基础上增加了超时限制，比如通过 <code>sleep（long millis）</code>方法或 <code>wait（long millis）</code>方法可以将 Java 线程置于 TIMED WAITING 状态。当超时时间到达后 Java 线程将会返回到 RUNNABLE 状态。当线程调用同步方法时，在没有获取到锁的情况下，线程将会进入到 <strong>BLOCKED（阻塞）</strong> 状态。线程在执行 Runnable 的<code>run()</code>方法之后将会进入到 <strong>TERMINATED（终止）</strong> 状态。</p><h2 id="什么是上下文切换"><a href="#什么是上下文切换" class="headerlink" title="什么是上下文切换?"></a>什么是上下文切换?</h2><p>多线程编程中一般线程的个数都大于 CPU 核心的个数，而一个 CPU 核心在任意时刻只能被一个线程使用，为了让这些线程都能得到有效执行，CPU 采取的策略是为每个线程分配时间片并轮转的形式。当一个线程的时间片用完的时候就会重新处于就绪状态让给其他线程使用，这个过程就属于一次上下文切换。</p><p>概括来说就是：当前任务在执行完 CPU 时间片切换到另一个任务之前会先保存自己的状态，以便下次再切换会这个任务时，可以再加载这个任务的状态。<strong>任务从保存到再加载的过程就是一次上下文切换</strong>。</p><p>上下文切换通常是计算密集型的。也就是说，它需要相当可观的处理器时间，在每秒几十上百次的切换中，每次切换都需要纳秒量级的时间。所以，上下文切换对系统来说意味着消耗大量的 CPU 时间，事实上，可能是操作系统中时间消耗最大的操作。 </p><p>Linux 相比与其他操作系统（包括其他类 Unix 系统）有很多的优点，其中有一项就是，其上下文切换和模式切换的时间消耗非常少。</p><h2 id="什么是线程死锁-如何避免死锁"><a href="#什么是线程死锁-如何避免死锁" class="headerlink" title="什么是线程死锁?如何避免死锁?"></a>什么是线程死锁?如何避免死锁?</h2><h3 id="认识线程死锁"><a href="#认识线程死锁" class="headerlink" title="认识线程死锁"></a>认识线程死锁</h3><blockquote><p>死锁经典问题与解决请参考<code>哲学家就餐问题</code></p></blockquote><p>多个线程同时被阻塞，它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞，因此程序不可能正常终止。</p><p>如下图所示，线程 A 持有资源 2，线程 B 持有资源 1，他们同时都想申请对方的资源，所以这两个线程就会互相等待而进入死锁状态。</p><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/09/05/17562020-cf7e-11e9-a36e-915d01a5fc8b.png" alt="死锁示例.png"></p><p>下面通过一个例子来说明线程死锁,代码模拟了上图的死锁的情况 (代码来源于《并发编程之美》)：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><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><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DeadLockDemo</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="type">Object</span> <span class="variable">resource1</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Object</span>();<span class="comment">//资源 1</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="type">Object</span> <span class="variable">resource2</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Object</span>();<span class="comment">//资源 2</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">Thread</span>(() -&gt; &#123;</span><br><span class="line">            <span class="keyword">synchronized</span> (resource1) &#123;</span><br><span class="line">                System.out.println(Thread.currentThread() + <span class="string">&quot;get resource1&quot;</span>);</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    Thread.sleep(<span class="number">1000</span>);</span><br><span class="line">                &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">                    e.printStackTrace();</span><br><span class="line">                &#125;</span><br><span class="line">                System.out.println(Thread.currentThread() + <span class="string">&quot;waiting get resource2&quot;</span>);</span><br><span class="line">                <span class="keyword">synchronized</span> (resource2) &#123;</span><br><span class="line">                    System.out.println(Thread.currentThread() + <span class="string">&quot;get resource2&quot;</span>);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;, <span class="string">&quot;线程 1&quot;</span>).start();</span><br><span class="line"></span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">Thread</span>(() -&gt; &#123;</span><br><span class="line">            <span class="keyword">synchronized</span> (resource2) &#123;</span><br><span class="line">                System.out.println(Thread.currentThread() + <span class="string">&quot;get resource2&quot;</span>);</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    Thread.sleep(<span class="number">1000</span>);</span><br><span class="line">                &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">                    e.printStackTrace();</span><br><span class="line">                &#125;</span><br><span class="line">                System.out.println(Thread.currentThread() + <span class="string">&quot;waiting get resource1&quot;</span>);</span><br><span class="line">                <span class="keyword">synchronized</span> (resource1) &#123;</span><br><span class="line">                    System.out.println(Thread.currentThread() + <span class="string">&quot;get resource1&quot;</span>);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;, <span class="string">&quot;线程 2&quot;</span>).start();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>Output</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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">Thread[线程 1,5,main]get resource1</span><br><span class="line">Thread[线程 2,5,main]get resource2</span><br><span class="line">Thread[线程 1,5,main]waiting get resource2</span><br><span class="line">Thread[线程 2,5,main]waiting get resource1</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>线程 A 通过 synchronized (resource1) 获得 resource1 的监视器锁，然后通过<code> Thread.sleep(1000);</code>让线程 A 休眠 1s 为的是让线程 B 得到执行然后获取到 resource2 的监视器锁。线程 A 和线程 B 休眠结束了都开始企图请求获取对方的资源，然后这两个线程就会陷入互相等待的状态，这也就产生了死锁。上面的例子符合产生死锁的四个必要条件。</p><p>学过操作系统的朋友都知道产生死锁必须具备以下四个条件：</p><ol><li>互斥条件：该资源任意一个时刻只由一个线程占用。</li><li>请求与保持条件：一个进程因请求资源而阻塞时，对已获得的资源保持不放。</li><li>不剥夺条件:线程已获得的资源在末使用完之前不能被其他线程强行剥夺，只有自己使用完毕后才释放资源。</li><li>循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。</li></ol><h3 id="如何避免线程死锁"><a href="#如何避免线程死锁" class="headerlink" title="如何避免线程死锁?"></a>如何避免线程死锁?</h3><p>我们只要破坏产生死锁的四个条件中的其中一个就可以了。</p><p><strong>破坏互斥条件</strong></p><p>这个条件我们没有办法破坏，因为我们用锁本来就是想让他们互斥的（临界资源需要互斥访问）。</p><p><strong>破坏请求与保持条件</strong></p><p>一次性申请所有的资源。</p><p><strong>破坏不剥夺条件</strong></p><p>占用部分资源的线程进一步申请其他资源时，如果申请不到，可以主动释放它占有的资源。</p><p><strong>破坏循环等待条件</strong></p><p>靠按序申请资源来预防。按某一顺序申请资源，释放资源则反序释放。破坏循环等待条件。</p><p>我们对线程 2 的代码修改成下面这样就不会产生死锁了。</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><br><span class="line"><span class="keyword">new</span> <span class="title class_">Thread</span>(() -&gt; &#123;</span><br><span class="line"><span class="keyword">synchronized</span> (resource1) &#123;</span><br><span class="line">System.out.println(Thread.currentThread() + <span class="string">&quot;get resource1&quot;</span>);</span><br><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">Thread.sleep(<span class="number">1000</span>);</span><br><span class="line">&#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">e.printStackTrace();</span><br><span class="line">&#125;</span><br><span class="line">System.out.println(Thread.currentThread() + <span class="string">&quot;waiting get resource2&quot;</span>);</span><br><span class="line"><span class="keyword">synchronized</span> (resource2) &#123;</span><br><span class="line">System.out.println(Thread.currentThread() + <span class="string">&quot;get resource2&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;, <span class="string">&quot;线程 2&quot;</span>).start();</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>Output</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"></span><br><span class="line">Thread[线程 1,5,main]get resource1</span><br><span class="line">Thread[线程 1,5,main]waiting get resource2</span><br><span class="line">Thread[线程 1,5,main]get resource2</span><br><span class="line">Thread[线程 2,5,main]get resource1</span><br><span class="line">Thread[线程 2,5,main]waiting get resource2</span><br><span class="line">Thread[线程 2,5,main]get resource2</span><br><span class="line"></span><br><span class="line">Process finished with exit code 0</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>我们分析一下上面的代码为什么避免了死锁的发生?</p><p><code>线程1</code>首先获得到 <code>resource1</code> 的监视器锁,这时候<code>线程2</code>就获取不到了。然后<code>线程1</code> 再去获取<code>resource2</code>的监视器锁，可以获取到。然后<code>线程1</code>释放了对<code>resource1</code>、<code>resource2</code>的监视器锁的占用，<code>线程2</code>获取到就可以执行了。这样就破坏了破坏循环等待条件，因此避免了死锁。</p><h2 id="说说-sleep-方法和-wait-方法区别和共同点"><a href="#说说-sleep-方法和-wait-方法区别和共同点" class="headerlink" title="说说 sleep() 方法和 wait() 方法区别和共同点?"></a>说说 sleep() 方法和 wait() 方法区别和共同点?</h2><ul><li>两者最主要的区别在于：<strong>sleep 方法没有释放锁，而 wait 方法释放了锁</strong> 。</li><li>两者都可以暂停线程的执行。</li><li>Wait 通常被用于线程间交互&#x2F;通信，sleep 通常被用于暂停执行。</li><li>wait() 方法被调用后，线程不会自动苏醒，需要别的线程调用同一个对象上的 notify() 或者 notifyAll() 方法。sleep() 方法执行完成后，线程会自动苏醒。或者可以使用wait(long timeout)超时后线程会自动苏醒。</li></ul><h2 id="为什么我们调用-start-方法时会执行-run-方法，为什么我们不能直接调用-run-方法？"><a href="#为什么我们调用-start-方法时会执行-run-方法，为什么我们不能直接调用-run-方法？" class="headerlink" title="为什么我们调用 start() 方法时会执行 run() 方法，为什么我们不能直接调用 run() 方法？"></a>为什么我们调用 start() 方法时会执行 run() 方法，为什么我们不能直接调用 run() 方法？</h2><p>这是另一个非常经典的 java 多线程面试问题，而且在面试中会经常被问到。很简单，但是很多人都会答不上来！</p><p>new 一个 Thread，线程进入了新建状态;调用 start() 方法，会启动一个线程并使线程进入了就绪状态，当分配到时间片后就可以开始运行了。 start() 会执行线程的相应准备工作，然后自动执行 run() 方法的内容，这是真正的多线程工作。 而直接执行 run() 方法，会把 run 方法当成一个 main 线程下的普通方法去执行，并不会在某个线程中执行它，所以这并不是多线程工作。</p><blockquote><p>调用 start 方法方可启动线程并使线程进入就绪状态，而 run 方法只是 thread 的一个普通方法调用，还是在主线程里执行。 </p></blockquote><h2 id="synchronized-关键字"><a href="#synchronized-关键字" class="headerlink" title="synchronized 关键字"></a>synchronized 关键字</h2><h3 id="说一说自己对于-synchronized-关键字的了解"><a href="#说一说自己对于-synchronized-关键字的了解" class="headerlink" title="说一说自己对于 synchronized 关键字的了解"></a>说一说自己对于 synchronized 关键字的了解</h3><p>synchronized关键字解决的是多个线程之间访问资源的同步性，synchronized关键字可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。</p><p>另外，在 Java 早期版本中，synchronized属于重量级锁，效率低下，因为监视器锁（monitor）是依赖于底层的操作系统的 Mutex Lock 来实现的，Java 的线程是映射到操作系统的原生线程之上的。如果要挂起或者唤醒一个线程，都需要操作系统帮忙完成，而操作系统实现线程之间的切换时需要从用户态转换到内核态，这个状态之间的转换需要相对比较长的时间，时间成本相对较高，这也是为什么早期的 synchronized 效率低的原因。庆幸的是在 Java 6 之后 Java 官方对从 JVM 层面对synchronized 较大优化，所以现在的 synchronized 锁效率也优化得很不错了。JDK1.6对锁的实现引入了大量的优化，如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。</p><h3 id="说说自己是怎么使用-synchronized-关键字，在项目中用到了吗"><a href="#说说自己是怎么使用-synchronized-关键字，在项目中用到了吗" class="headerlink" title="说说自己是怎么使用 synchronized 关键字，在项目中用到了吗"></a>说说自己是怎么使用 synchronized 关键字，在项目中用到了吗</h3><p><strong>synchronized关键字最主要的三种使用方式：</strong></p><ul><li><strong>修饰实例方法:</strong> 作用于当前对象实例加锁，进入同步代码前要获得当前对象实例的锁</li><li><strong>修饰静态方法:</strong> :也就是给当前类加锁，会作用于类的所有对象实例，因为静态成员不属于任何一个实例对象，是类成员（ static 表明这是该类的一个静态资源，不管new了多少个对象，只有一份）。所以如果一个线程A调用一个实例对象的非静态 synchronized 方法，而线程B需要调用这个实例对象所属类的静态 synchronized 方法，是允许的，不会发生互斥现象，<strong>因为访问静态 synchronized 方法占用的锁是当前类的锁，而访问非静态 synchronized 方法占用的锁是当前实例对象锁</strong>。</li><li><strong>修饰代码块:</strong> 指定加锁对象，对给定对象加锁，进入同步代码库前要获得给定对象的锁。</li></ul><p><strong>总结：</strong> synchronized 关键字加到 static 静态方法和 synchronized(class)代码块上都是是给 Class 类上锁。synchronized 关键字加到实例方法上是给对象实例上锁。尽量不要使用 synchronized(String a) 因为JVM中，字符串常量池具有缓存功能！</p><p>下面我以一个常见的面试题为例讲解一下 synchronized 关键字的具体使用。</p><p>面试中面试官经常会说：“单例模式了解吗？来给我手写一下！给我解释一下双重检验锁方式实现单例模式的原理呗！”</p><p><strong>双重校验锁实现对象单例（线程安全）</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Singleton</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">volatile</span> <span class="keyword">static</span> Singleton uniqueInstance;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="title function_">Singleton</span><span class="params">()</span> &#123;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> Singleton <span class="title function_">getUniqueInstance</span><span class="params">()</span> &#123;</span><br><span class="line">       <span class="comment">//先判断对象是否已经实例过，没有实例化过才进入加锁代码</span></span><br><span class="line">        <span class="keyword">if</span> (uniqueInstance == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="comment">//类对象加锁</span></span><br><span class="line">            <span class="keyword">synchronized</span> (Singleton.class) &#123;</span><br><span class="line">                <span class="keyword">if</span> (uniqueInstance == <span class="literal">null</span>) &#123;</span><br><span class="line">                    uniqueInstance = <span class="keyword">new</span> <span class="title class_">Singleton</span>();</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> uniqueInstance;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>另外，需要注意 uniqueInstance 采用 volatile 关键字修饰也是很有必要。</p><p>uniqueInstance 采用 volatile 关键字修饰也是很有必要的， uniqueInstance &#x3D; new Singleton(); 这段代码其实是分为三步执行：</p><ol><li>为 uniqueInstance 分配内存空间</li><li>初始化 uniqueInstance</li><li>将 uniqueInstance 指向分配的内存地址</li></ol><p>但是由于 JVM 具有指令重排的特性，执行顺序有可能变成 1-&gt;3-&gt;2。指令重排在单线程环境下不会出先问题，但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如，线程 T1 执行了 1 和 3，此时 T2 调用 getUniqueInstance() 后发现 uniqueInstance 不为空，因此返回 uniqueInstance，但此时 uniqueInstance 还未被初始化。</p><p>使用 volatile 可以禁止 JVM 的指令重排，保证在多线程环境下也能正常运行。</p><h3 id="1-3-讲一下-synchronized-关键字的底层原理"><a href="#1-3-讲一下-synchronized-关键字的底层原理" class="headerlink" title="1.3. 讲一下 synchronized 关键字的底层原理"></a>1.3. 讲一下 synchronized 关键字的底层原理</h3><p><strong>synchronized 关键字底层原理属于 JVM 层面。</strong></p><p><strong>① synchronized 同步语句块的情况</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SynchronizedDemo</span> &#123;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">method</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;synchronized 代码块&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"></span><br></pre></td></tr></table></figure><p>通过 JDK 自带的 javap 命令查看 SynchronizedDemo 类的相关字节码信息：首先切换到类的对应目录执行 <code>javac SynchronizedDemo.java</code> 命令生成编译后的 .class 文件，然后执行<code>javap -c -s -v -l SynchronizedDemo.class</code>。<br><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/09/05/852743d0-cf7f-11e9-a36e-915d01a5fc8b.png" alt="synchronized关键字原理.png.png"></p><p>从上面我们可以看出：</p><p><strong>synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令，其中 monitorenter 指令指向同步代码块的开始位置，monitorexit 指令则指明同步代码块的结束位置。</strong> 当执行 monitorenter 指令时，线程试图获取锁也就是获取 monitor(monitor对象存在于每个Java对象的对象头中，synchronized 锁便是通过这种方式获取锁的，也是为什么Java中任意对象可以作为锁的原因) 的持有权。当计数器为0则可以成功获取，获取后将锁计数器设为1也就是加1。相应的在执行 monitorexit 指令后，将锁计数器设为0，表明锁被释放。如果获取对象锁失败，那当前线程就要阻塞等待，直到锁被另外一个线程释放为止。</p><p><strong>② synchronized 修饰方法的的情况</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SynchronizedDemo2</span> &#123;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title function_">method</span><span class="params">()</span> &#123;</span><br><span class="line">System.out.println(<span class="string">&quot;synchronized 方法&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/09/05/45e78db0-cf7f-11e9-a36e-915d01a5fc8b.png" alt="synchronized关键字原理.png"></p><p>synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令，取得代之的确实是 ACC_SYNCHRONIZED 标识，该标识指明了该方法是一个同步方法，JVM 通过该 ACC_SYNCHRONIZED 访问标志来辨别一个方法是否声明为同步方法，从而执行相应的同步调用。</p><h3 id="说说-JDK1-6-之后的synchronized-关键字底层做了哪些优化，可以详细介绍一下这些优化吗"><a href="#说说-JDK1-6-之后的synchronized-关键字底层做了哪些优化，可以详细介绍一下这些优化吗" class="headerlink" title="说说 JDK1.6 之后的synchronized 关键字底层做了哪些优化，可以详细介绍一下这些优化吗"></a>说说 JDK1.6 之后的synchronized 关键字底层做了哪些优化，可以详细介绍一下这些优化吗</h3><p>JDK1.6 对锁的实现引入了大量的优化，如偏向锁、轻量级锁、自旋锁、适应性自旋锁、锁消除、锁粗化等技术来减少锁操作的开销。</p><p>锁主要存在四种状态，依次是：无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态，他们会随着竞争的激烈而逐渐升级。注意锁可以升级不可降级，这种策略是为了提高获得锁和释放锁的效率。</p><h3 id="谈谈-synchronized和ReentrantLock-的区别"><a href="#谈谈-synchronized和ReentrantLock-的区别" class="headerlink" title="谈谈 synchronized和ReentrantLock 的区别"></a>谈谈 synchronized和ReentrantLock 的区别</h3><p><strong>1. 两者都是可重入锁</strong><br>两者都是可重入锁。“可重入锁”概念是：自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁，此时这个对象锁还没有释放，当其再次想要获取这个对象的锁的时候还是可以获取的，如果不可锁重入的话，就会造成死锁。同一个线程每次获取锁，锁的计数器都自增1，所以要等到锁的计数器下降为0时才能释放锁。<br><strong>2. synchronized 依赖于 JVM 而 ReentrantLock 依赖于 API</strong><br>synchronized 是依赖于 JVM 实现的，前面我们也讲到了 虚拟机团队在 JDK1.6 为 synchronized 关键字进行了很多优化，但是这些优化都是在虚拟机层面实现的，并没有直接暴露给我们。ReentrantLock 是 JDK 层面实现的（也就是 API 层面，需要 lock() 和 unlock() 方法配合 try&#x2F;finally 语句块来完成），所以我们可以通过查看它的源代码，来看它是如何实现的。<br><strong>3. ReentrantLock 比 synchronized 增加了一些高级功能</strong><br>相比synchronized，ReentrantLock增加了一些高级功能。主要来说主要有三点：</p><ol><li>等待可中断；</li><li>可实现公平锁；</li><li>可实现选择性通知（锁可以绑定多个条件）</li></ol><ul><li><strong>ReentrantLock提供了一种能够中断等待锁的线程的机制</strong>，通过lock.lockInterruptibly()来实现这个机制。也就是说正在等待的线程可以选择放弃等待，改为处理其他事情。</li><li><strong>ReentrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。</strong> ReentrantLock默认情况是非公平的，可以通过 ReentrantLock类的<code>ReentrantLock(boolean fair)</code>构造方法来制定是否是公平的。</li><li>synchronized关键字与wait()和notify()&#x2F;notifyAll()方法相结合可以实现等待&#x2F;通知机制，ReentrantLock类当然也可以实现，但是需要借助于Condition接口与newCondition() 方法。Condition是JDK1.5之后才有的，它具有很好的灵活性，比如可以实现多路通知功能也就是在一个Lock对象中可以创建多个Condition实例（即对象监视器），<strong>线程对象可以注册在指定的Condition中，从而可以有选择性的进行线程通知，在调度线程上更加灵活。 在使用notify()&#x2F;notifyAll()方法进行通知时，被通知的线程是由 JVM 选择的，用ReentrantLock类结合Condition实例可以实现“选择性通知”</strong> ，这个功能非常重要，而且是Condition接口默认提供的。而synchronized关键字就相当于整个Lock对象中只有一个Condition实例，所有的线程都注册在它一个身上。如果执行notifyAll()方法的话就会通知所有处于等待状态的线程这样会造成很大的效率问题，而Condition实例的signalAll()方法 只会唤醒注册在该Condition实例中的所有等待线程。<br>如果你想使用上述功能，那么选择ReentrantLock是一个不错的选择。</li></ul><p><strong>4. 性能已不是选择标准</strong></p><h2 id="volatile关键字"><a href="#volatile关键字" class="headerlink" title="volatile关键字"></a>volatile关键字</h2><h3 id="讲一下Java内存模型"><a href="#讲一下Java内存模型" class="headerlink" title="讲一下Java内存模型"></a>讲一下Java内存模型</h3><p>在 JDK1.2 之前，Java的内存模型实现总是从<strong>主存</strong>（即共享内存）读取变量，是不需要进行特别的注意的。而在当前的 Java 内存模型下，线程可以把变量保存<strong>本地内存</strong>比如机器的寄存器）中，而不是直接在主存中进行读写。这就可能造成一个线程在主存中修改了一个变量的值，而另外一个线程还继续使用它在寄存器中的变量值的拷贝，造成<strong>数据的不一致</strong>。</p><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/09/05/0e55ac50-cf80-11e9-a36e-915d01a5fc8b.png" alt="数据不一致.png"></p><p>要解决这个问题，就需要把变量声明为<strong>volatile</strong>，这就指示 JVM，这个变量是不稳定的，每次使用它都到主存中进行读取。</p><p>说白了， <strong>volatile</strong> 关键字的主要作用就是保证变量的可见性然后还有一个作用是防止指令重排序。</p><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/09/05/252228f0-cf80-11e9-a36e-915d01a5fc8b.png" alt="volatile关键字的可见性.png"></p><h3 id="说说-synchronized-关键字和-volatile-关键字的区别"><a href="#说说-synchronized-关键字和-volatile-关键字的区别" class="headerlink" title="说说 synchronized 关键字和 volatile 关键字的区别"></a>说说 synchronized 关键字和 volatile 关键字的区别</h3><p><code>synchronized</code>关键字和<code>volatile</code>关键字比较</p><ul><li><strong>volatile关键字</strong>是线程同步的<strong>轻量级实现</strong>，所以<strong>volatile性能肯定比synchronized关键字要好</strong>。但是<strong>volatile关键字只能用于变量而synchronized关键字可以修饰方法以及代码块</strong>。synchronized关键字在JavaSE1.6之后进行了主要包括为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁以及其它各种优化之后执行效率有了显著提升，<strong>实际开发中使用 synchronized 关键字的场景还是更多一些</strong>。</li><li><strong>多线程访问volatile关键字不会发生阻塞，而synchronized关键字可能会发生阻塞</strong></li><li><strong>volatile关键字能保证数据的可见性，但不能保证数据的原子性。synchronized关键字两者都能保证。</strong></li><li><strong>volatile关键字主要用于解决变量在多个线程之间的可见性，而 synchronized关键字解决的是多个线程之间访问资源的同步性。</strong></li></ul><h2 id="ThreadLocal"><a href="#ThreadLocal" class="headerlink" title="ThreadLocal"></a>ThreadLocal</h2><h3 id="ThreadLocal简介"><a href="#ThreadLocal简介" class="headerlink" title="ThreadLocal简介"></a>ThreadLocal简介</h3><p>通常情况下，我们创建的变量是可以被任何一个线程访问并修改的。<strong>如果想实现每一个线程都有自己的专属本地变量该如何解决呢？</strong> JDK中提供的<code>ThreadLocal</code>类正是为了解决这样的问题。 <strong><code>ThreadLocal</code>类主要解决的就是让每个线程绑定自己的值，可以将<code>ThreadLocal</code>类形象的比喻成存放数据的盒子，盒子中可以存储每个线程的私有数据。</strong></p><p><strong>如果你创建了一个<code>ThreadLocal</code>变量，那么访问这个变量的每个线程都会有这个变量的本地副本，这也是<code>ThreadLocal</code>变量名的由来。他们可以使用 <code>get()</code> 和 <code>set()</code> 方法来获取默认值或将其值更改为当前线程所存的副本的值，从而避免了线程安全问题。</strong></p><p>再举个简单的例子： </p><p>比如有两个人去宝屋收集宝物，这两个共用一个袋子的话肯定会产生争执，但是给他们两个人每个人分配一个袋子的话就不会出现这样的问题。如果把这两个人比作线程的话，那么ThreadLocal就是用来避免这两个线程竞争的。</p><h3 id="ThreadLocal示例"><a href="#ThreadLocal示例" class="headerlink" title="ThreadLocal示例"></a>ThreadLocal示例</h3><p>相信看了上面的解释，大家已经搞懂 ThreadLocal 类是个什么东西了。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.text.SimpleDateFormat;</span><br><span class="line"><span class="keyword">import</span> java.util.Random;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ThreadLocalExample</span> <span class="keyword">implements</span> <span class="title class_">Runnable</span>&#123;</span><br><span class="line"></span><br><span class="line">     <span class="comment">// SimpleDateFormat 不是线程安全的，所以每个线程都要有自己独立的副本</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> ThreadLocal&lt;SimpleDateFormat&gt; formatter = ThreadLocal.withInitial(() -&gt; <span class="keyword">new</span> <span class="title class_">SimpleDateFormat</span>(<span class="string">&quot;yyyyMMdd HHmm&quot;</span>));</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> InterruptedException &#123;</span><br><span class="line">        <span class="type">ThreadLocalExample</span> <span class="variable">obj</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ThreadLocalExample</span>();</span><br><span class="line">        <span class="keyword">for</span>(<span class="type">int</span> i=<span class="number">0</span> ; i&lt;<span class="number">10</span>; i++)&#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>(obj, <span class="string">&quot;&quot;</span>+i);</span><br><span class="line">            Thread.sleep(<span class="keyword">new</span> <span class="title class_">Random</span>().nextInt(<span class="number">1000</span>));</span><br><span class="line">            t.start();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;Thread Name= &quot;</span>+Thread.currentThread().getName()+<span class="string">&quot; default Formatter = &quot;</span>+formatter.get().toPattern());</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            Thread.sleep(<span class="keyword">new</span> <span class="title class_">Random</span>().nextInt(<span class="number">1000</span>));</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">//formatter pattern is changed here by thread, but it won&#x27;t reflect to other threads</span></span><br><span class="line">        formatter.set(<span class="keyword">new</span> <span class="title class_">SimpleDateFormat</span>());</span><br><span class="line"></span><br><span class="line">        System.out.println(<span class="string">&quot;Thread Name= &quot;</span>+Thread.currentThread().getName()+<span class="string">&quot; formatter = &quot;</span>+formatter.get().toPattern());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>Output:</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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">Thread Name= 0 default Formatter = yyyyMMdd HHmm</span><br><span class="line">Thread Name= 0 formatter = yy-M-d ah:mm</span><br><span class="line">Thread Name= 1 default Formatter = yyyyMMdd HHmm</span><br><span class="line">Thread Name= 2 default Formatter = yyyyMMdd HHmm</span><br><span class="line">Thread Name= 1 formatter = yy-M-d ah:mm</span><br><span class="line">Thread Name= 3 default Formatter = yyyyMMdd HHmm</span><br><span class="line">Thread Name= 2 formatter = yy-M-d ah:mm</span><br><span class="line">Thread Name= 4 default Formatter = yyyyMMdd HHmm</span><br><span class="line">Thread Name= 3 formatter = yy-M-d ah:mm</span><br><span class="line">Thread Name= 4 formatter = yy-M-d ah:mm</span><br><span class="line">Thread Name= 5 default Formatter = yyyyMMdd HHmm</span><br><span class="line">Thread Name= 5 formatter = yy-M-d ah:mm</span><br><span class="line">Thread Name= 6 default Formatter = yyyyMMdd HHmm</span><br><span class="line">Thread Name= 6 formatter = yy-M-d ah:mm</span><br><span class="line">Thread Name= 7 default Formatter = yyyyMMdd HHmm</span><br><span class="line">Thread Name= 7 formatter = yy-M-d ah:mm</span><br><span class="line">Thread Name= 8 default Formatter = yyyyMMdd HHmm</span><br><span class="line">Thread Name= 9 default Formatter = yyyyMMdd HHmm</span><br><span class="line">Thread Name= 8 formatter = yy-M-d ah:mm</span><br><span class="line">Thread Name= 9 formatter = yy-M-d ah:mm</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>从输出中可以看出，Thread-0已经改变了formatter的值，但仍然是thread-2默认格式化程序与初始化值相同，其他线程也一样。</p><p>上面有一段代码用到了创建 <code>ThreadLocal</code> 变量的那段代码用到了 Java8 的知识，它等于下面这段代码，如果你写了下面这段代码的话，IDEA会提示你转换为Java8的格式(IDEA真的不错！)。因为ThreadLocal类在Java 8中扩展，使用一个新的方法<code>withInitial()</code>，将Supplier功能接口作为参数。</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><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> ThreadLocal&lt;SimpleDateFormat&gt; formatter = <span class="keyword">new</span> <span class="title class_">ThreadLocal</span>&lt;SimpleDateFormat&gt;()&#123;</span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">protected</span> SimpleDateFormat <span class="title function_">initialValue</span><span class="params">()</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">SimpleDateFormat</span>(<span class="string">&quot;yyyyMMdd HHmm&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="ThreadLocal原理"><a href="#ThreadLocal原理" class="headerlink" title="ThreadLocal原理"></a>ThreadLocal原理</h3><p>从 <code>Thread</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><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Thread</span> <span class="keyword">implements</span> <span class="title class_">Runnable</span> &#123;</span><br><span class="line"> ......</span><br><span class="line"><span class="comment">//与此线程有关的ThreadLocal值。由ThreadLocal类维护</span></span><br><span class="line">ThreadLocal.<span class="type">ThreadLocalMap</span> <span class="variable">threadLocals</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">//与此线程有关的InheritableThreadLocal值。由InheritableThreadLocal类维护</span></span><br><span class="line">ThreadLocal.<span class="type">ThreadLocalMap</span> <span class="variable">inheritableThreadLocals</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line"> ......</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>从上面<code>Thread</code>类 源代码可以看出<code>Thread</code> 类中有一个 <code>threadLocals</code> 和 一个  <code>inheritableThreadLocals</code> 变量，它们都是 <code>ThreadLocalMap</code>  类型的变量,我们可以把 <code>ThreadLocalMap</code>  理解为<code>ThreadLocal</code> 类实现的定制化的 <code>HashMap</code>。默认情况下这两个变量都是null，只有当前线程调用 <code>ThreadLocal</code> 类的 <code>set</code>或<code>get</code>方法时才创建它们，实际上调用这两个方法的时候，我们调用的是<code>ThreadLocalMap</code>类对应的 <code>get()</code>、<code>set() </code>方法。</p><p><code>ThreadLocal</code>类的<code>set()</code>方法</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">set</span><span class="params">(T value)</span> &#123;</span><br><span class="line"><span class="type">Thread</span> <span class="variable">t</span> <span class="operator">=</span> Thread.currentThread();</span><br><span class="line"><span class="type">ThreadLocalMap</span> <span class="variable">map</span> <span class="operator">=</span> getMap(t);</span><br><span class="line"><span class="keyword">if</span> (map != <span class="literal">null</span>)</span><br><span class="line">map.set(<span class="built_in">this</span>, value);</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">createMap(t, value);</span><br><span class="line">&#125;</span><br><span class="line">ThreadLocalMap <span class="title function_">getMap</span><span class="params">(Thread t)</span> &#123;</span><br><span class="line"><span class="keyword">return</span> t.threadLocals;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>通过上面这些内容，我们足以通过猜测得出结论：<strong>最终的变量是放在了当前线程的 <code>ThreadLocalMap</code> 中，并不是存在 <code>ThreadLocal</code> 上，<code>ThreadLocal</code> 可以理解为只是<code>ThreadLocalMap</code>的封装，传递了变量值。</strong> <code>ThrealLocal</code> 类中可以通过<code>Thread.currentThread()</code>获取到当前线程对象后，直接通过<code>getMap(Thread t)</code>可以访问到该线程的<code>ThreadLocalMap</code>对象。</p><p><strong>每个<code>Thread</code>中都具备一个<code>ThreadLocalMap</code>，而<code>ThreadLocalMap</code>可以存储以<code>ThreadLocal</code>为key的键值对。</strong> 比如我们在同一个线程中声明了两个 <code>ThreadLocal</code> 对象的话，会使用 <code>Thread</code>内部都是使用仅有那个<code>ThreadLocalMap</code> 存放数据的，<code>ThreadLocalMap</code>的 key 就是 <code>ThreadLocal</code>对象，value 就是 <code>ThreadLocal</code> 对象调用<code>set</code>方法设置的值。<code>ThreadLocal</code> 是 map结构是为了让每个线程可以关联多个 <code>ThreadLocal</code>变量。这也就解释了 ThreadLocal 声明的变量为什么在每一个线程都有自己的专属本地变量。</p><p><code>ThreadLocalMap</code>是<code>ThreadLocal</code>的静态内部类。</p><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/09/05/711b5150-cf80-11e9-a36e-915d01a5fc8b.png" alt="ThreadLocal内部类.png"></p><h3 id="ThreadLocal-内存泄露问题"><a href="#ThreadLocal-内存泄露问题" class="headerlink" title="ThreadLocal 内存泄露问题"></a>ThreadLocal 内存泄露问题</h3><p><code>ThreadLocalMap</code> 中使用的 key 为 <code>ThreadLocal</code> 的弱引用,而 value 是强引用。所以，如果 <code>ThreadLocal</code> 没有被外部强引用的情况下，在垃圾回收的时候会 key 会被清理掉，而 value 不会被清理掉。这样一来，<code>ThreadLocalMap</code> 中就会出现key为null的Entry。假如我们不做任何措施的话，value 永远无法被GC 回收，这个时候就可能会产生内存泄露。ThreadLocalMap实现中已经考虑了这种情况，在调用 <code>set()</code>、<code>get()</code>、<code>remove()</code> 方法的时候，会清理掉 key 为 null 的记录。使用完 <code>ThreadLocal</code>方法后 最好手动调用<code>remove()</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><br><span class="line"><span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Entry</span> <span class="keyword">extends</span> <span class="title class_">WeakReference</span>&lt;ThreadLocal&lt;?&gt;&gt; &#123;</span><br><span class="line"><span class="comment">/** The value associated with this ThreadLocal. */</span></span><br><span class="line">Object value;</span><br><span class="line"></span><br><span class="line">Entry(ThreadLocal&lt;?&gt; k, Object v) &#123;</span><br><span class="line"><span class="built_in">super</span>(k);</span><br><span class="line">value = v;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><strong>弱引用介绍：</strong></p><blockquote><p>如果一个对象只具有弱引用，那就类似于<strong>可有可无的生活用品</strong>。弱引用与软引用的区别在于：只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它 所管辖的内存区域的过程中，一旦发现了只具有弱引用的对象，不管当前内存空间足够与否，都会回收它的内存。不过，由于垃圾回收器是一个优先级很低的线程， 因此不一定会很快发现那些只具有弱引用的对象。<br>弱引用可以和一个引用队列(ReferenceQueue)联合使用，如果弱引用所引用的对象被垃圾回收，Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。</p></blockquote><h2 id="线程池"><a href="#线程池" class="headerlink" title="线程池"></a>线程池</h2><h3 id="为什么要用线程池？"><a href="#为什么要用线程池？" class="headerlink" title="为什么要用线程池？"></a>为什么要用线程池？</h3><p>线程池提供了一种限制和管理资源(包括执行一个任务)。 每个线程池还维护一些基本统计信息，例如已完成任务的数量。 </p><p>这里借用《Java并发编程的艺术》提到的来说一下使用线程池的好处：</p><ul><li><strong>降低资源消耗。</strong> 通过重复利用已创建的线程降低线程创建和销毁造成的消耗。</li><li><strong>提高响应速度。</strong> 当任务到达时，任务可以不需要的等到线程创建就能立即执行。</li><li><strong>提高线程的可管理性。</strong> 线程是稀缺资源，如果无限制的创建，不仅会消耗系统资源，还会降低系统的稳定性，使用线程池可以进行统一的分配，调优和监控。</li></ul><h3 id="实现Runnable接口和Callable接口的区别"><a href="#实现Runnable接口和Callable接口的区别" class="headerlink" title="实现Runnable接口和Callable接口的区别"></a>实现Runnable接口和Callable接口的区别</h3><p>如果想让线程池执行任务的话需要实现的Runnable接口或Callable接口。 Runnable接口或Callable接口实现类都可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor执行。两者的区别在于 Runnable 接口不会返回结果但是 Callable 接口可以返回结果。</p><blockquote><p>备注： 工具类<code>Executors</code>可以实现<code>Runnable</code>对象和<code>Callable</code>对象之间的相互转换。(<code>Executors.callable(Runnable task)</code>或<code>Executors.callable(Runnable task，Object resule)</code>)。</p></blockquote><h3 id="执行execute-方法和submit-方法的区别是什么呢？"><a href="#执行execute-方法和submit-方法的区别是什么呢？" class="headerlink" title="执行execute()方法和submit()方法的区别是什么呢？"></a>执行execute()方法和submit()方法的区别是什么呢？</h3><p>  1)<strong><code>execute()</code> 方法用于提交不需要返回值的任务，所以无法判断任务是否被线程池执行成功与否；</strong></p><p>  2)<strong><code>submit()</code> 方法用于提交需要返回值的任务。线程池会返回一个Future类型的对象，通过这个Future对象可以判断任务是否执行成功</strong>，并且可以通过future的get()方法来获取返回值，get()方法会阻塞当前线程直到任务完成，而使用 <code>get(long timeout，TimeUnit unit)</code>方法则会阻塞当前线程一段时间后立即返回，这时候有可能任务没有执行完。</p><h3 id="如何创建线程池"><a href="#如何创建线程池" class="headerlink" title="如何创建线程池"></a>如何创建线程池</h3><p>《阿里巴巴Java开发手册》中强制线程池不允许使用 Executors 去创建，而是通过 ThreadPoolExecutor 的方式，这样的处理方式让写的同学更加明确线程池的运行规则，规避资源耗尽的风险</p><blockquote><p>Executors 返回线程池对象的弊端如下：</p><ul><li><strong>FixedThreadPool 和 SingleThreadExecutor</strong> ： 允许请求的队列长度为 Integer.MAX_VALUE ，可能堆积大量的请求，从而导致OOM。</li><li><strong>CachedThreadPool 和 ScheduledThreadPool</strong> ： 允许创建的线程数量为 Integer.MAX_VALUE ，可能会创建大量线程，从而导致OOM。</li></ul></blockquote><p><strong>方式一：通过构造方法实现</strong><br><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/09/05/9dac9e90-cf80-11e9-a36e-915d01a5fc8b.png" alt="ThreadPoolExecutor构造方法.png"><br><strong>方式二：通过Executor 框架的工具类Executors来实现</strong><br>我们可以创建三种类型的ThreadPoolExecutor：</p><ul><li><strong>FixedThreadPool</strong> ： 该方法返回一个固定线程数量的线程池。该线程池中的线程数量始终不变。当有一个新的任务提交时，线程池中若有空闲线程，则立即执行。若没有，则新的任务会被暂存在一个任务队列中，待有线程空闲时，便处理在任务队列中的任务。</li><li><strong>SingleThreadExecutor：</strong> 方法返回一个只有一个线程的线程池。若多余一个任务被提交到该线程池，任务会被保存在一个任务队列中，待线程空闲，按先入先出的顺序执行队列中的任务。</li><li><strong>CachedThreadPool：</strong> 该方法返回一个可根据实际情况调整线程数量的线程池。线程池的线程数量不确定，但若有空闲线程可以复用，则会优先使用可复用的线程。若所有线程均在工作，又有新的任务提交，则会创建新的线程处理任务。所有线程在当前任务执行完毕后，将返回线程池进行复用。</li></ul><p>对应Executors工具类中的方法如图所示：<br><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/09/05/b3f67db0-cf80-11e9-a36e-915d01a5fc8b.png" alt="image.png"></p><h2 id="Atomic-原子类"><a href="#Atomic-原子类" class="headerlink" title="Atomic 原子类"></a>Atomic 原子类</h2><h3 id="介绍一下Atomic-原子类"><a href="#介绍一下Atomic-原子类" class="headerlink" title="介绍一下Atomic 原子类"></a>介绍一下Atomic 原子类</h3><p>Atomic 翻译成中文是原子的意思。在化学上，我们知道原子是构成一般物质的最小单位，在化学反应中是不可分割的。在我们这里 Atomic 是指一个操作是不可中断的。即使是在多个线程一起执行的时候，一个操作一旦开始，就不会被其他线程干扰。</p><p>所以，所谓原子类说简单点就是具有原子&#x2F;原子操作特征的类。</p><p>并发包 <code>java.util.concurrent</code> 的原子类都存放在<code>java.util.concurrent.atomic</code>下,如下图所示。<br><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/09/05/c90f2fd0-cf80-11e9-a36e-915d01a5fc8b.png" alt="JUC原子类概览.png"></p><h3 id="JUC-包中的原子类是哪4类"><a href="#JUC-包中的原子类是哪4类" class="headerlink" title="JUC 包中的原子类是哪4类?"></a>JUC 包中的原子类是哪4类?</h3><p><strong>基本类型</strong> </p><p>使用原子的方式更新基本类型</p><ul><li>AtomicInteger：整形原子类</li><li>AtomicLong：长整型原子类</li><li>AtomicBoolean：布尔型原子类</li></ul><p><strong>数组类型</strong></p><p>使用原子的方式更新数组里的某个元素</p><ul><li>AtomicIntegerArray：整形数组原子类</li><li>AtomicLongArray：长整形数组原子类</li><li>AtomicReferenceArray：引用类型数组原子类</li></ul><p><strong>引用类型</strong></p><ul><li>AtomicReference：引用类型原子类</li><li>AtomicStampedReference：原子更新引用类型里的字段原子类</li><li>AtomicMarkableReference ：原子更新带有标记位的引用类型</li></ul><p><strong>对象的属性修改类型</strong></p><ul><li>AtomicIntegerFieldUpdater：原子更新整形字段的更新器</li><li>AtomicLongFieldUpdater：原子更新长整形字段的更新器</li><li>AtomicStampedReference：原子更新带有版本号的引用类型。该类将整数值与引用关联起来，可用于解决原子的更新数据和数据的版本号，可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。</li></ul><h3 id="讲讲-AtomicInteger-的使用"><a href="#讲讲-AtomicInteger-的使用" class="headerlink" title="讲讲 AtomicInteger 的使用"></a>讲讲 AtomicInteger 的使用</h3><p> <strong>AtomicInteger 类常用方法</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="type">int</span> <span class="title function_">get</span><span class="params">()</span> <span class="comment">//获取当前的值</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="type">int</span> <span class="title function_">getAndSet</span><span class="params">(<span class="type">int</span> newValue)</span><span class="comment">//获取当前的值，并设置新的值</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="type">int</span> <span class="title function_">getAndIncrement</span><span class="params">()</span><span class="comment">//获取当前的值，并自增</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="type">int</span> <span class="title function_">getAndDecrement</span><span class="params">()</span> <span class="comment">//获取当前的值，并自减</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="type">int</span> <span class="title function_">getAndAdd</span><span class="params">(<span class="type">int</span> delta)</span> <span class="comment">//获取当前的值，并加上预期的值</span></span><br><span class="line"><span class="type">boolean</span> <span class="title function_">compareAndSet</span><span class="params">(<span class="type">int</span> expect, <span class="type">int</span> update)</span> <span class="comment">//如果输入的数值等于预期值，则以原子方式将该值设置为输入值(update)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title function_">lazySet</span><span class="params">(<span class="type">int</span> newValue)</span><span class="comment">//最终设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p> <strong>AtomicInteger 类的使用示例</strong></p><p>使用 AtomicInteger 之后，不用对 increment() 方法加锁也可以保证线程安全。</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><br><span class="line"><span class="keyword">class</span> <span class="title class_">AtomicIntegerTest</span> &#123;</span><br><span class="line">        <span class="keyword">private</span> <span class="type">AtomicInteger</span> <span class="variable">count</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicInteger</span>();</span><br><span class="line">      <span class="comment">//使用AtomicInteger之后，不需要对该方法加锁，也可以实现线程安全。</span></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">increment</span><span class="params">()</span> &#123;</span><br><span class="line">                  count.incrementAndGet();</span><br><span class="line">        &#125;</span><br><span class="line">     </span><br><span class="line">       <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">getCount</span><span class="params">()</span> &#123;</span><br><span class="line">                <span class="keyword">return</span> count.get();</span><br><span class="line">        &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="简单介绍一下-AtomicInteger-类的原理"><a href="#简单介绍一下-AtomicInteger-类的原理" class="headerlink" title="简单介绍一下 AtomicInteger 类的原理"></a>简单介绍一下 AtomicInteger 类的原理</h3><p>AtomicInteger 线程安全原理简单分析</p><p>AtomicInteger 类的部分源码：</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><br><span class="line"><span class="comment">// setup to use Unsafe.compareAndSwapInt for updates(更新操作时提供“比较并替换”的作用)</span></span><br><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">unsafe</span> <span class="operator">=</span> Unsafe.getUnsafe();</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">long</span> valueOffset;</span><br><span class="line"></span><br><span class="line"><span class="keyword">static</span> &#123;</span><br><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">valueOffset = unsafe.objectFieldOffset</span><br><span class="line">(AtomicInteger.class.getDeclaredField(<span class="string">&quot;value&quot;</span>));</span><br><span class="line">&#125; <span class="keyword">catch</span> (Exception ex) &#123; <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Error</span>(ex); &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">volatile</span> <span class="type">int</span> value;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>AtomicInteger 类主要利用 CAS (compare and swap) + volatile 和 native 方法来保证原子操作，从而避免 synchronized 的高开销，执行效率大为提升。</p><p>CAS的原理是拿期望的值和原本的一个值作比较，如果相同则更新成新的值。UnSafe 类的 objectFieldOffset() 方法是一个本地方法，这个方法是用来拿到“原来的值”的内存地址，返回值是 valueOffset。另外 value 是一个volatile变量，在内存中可见，因此 JVM 可以保证任何时刻任何线程总能拿到该变量的最新值。</p><h2 id="AQS"><a href="#AQS" class="headerlink" title="AQS"></a>AQS</h2><h3 id="AQS-介绍"><a href="#AQS-介绍" class="headerlink" title="AQS 介绍"></a>AQS 介绍</h3><p>AQS的全称为(AbstractQueuedSynchronizer)，这个类在java.util.concurrent.locks包下面。</p><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/09/05/fad0eb30-cf80-11e9-a36e-915d01a5fc8b.png" alt="AQS类.png"></p><p>AQS是一个用来构建锁和同步器的框架，使用AQS能简单且高效地构造出应用广泛的大量的同步器，比如我们提到的ReentrantLock，Semaphore，其他的诸如ReentrantReadWriteLock，SynchronousQueue，FutureTask等等皆是基于AQS的。当然，我们自己也能利用AQS非常轻松容易地构造出符合我们自己需求的同步器。</p><h3 id="AQS-原理分析"><a href="#AQS-原理分析" class="headerlink" title="AQS 原理分析"></a>AQS 原理分析</h3><blockquote><p>在面试中被问到并发知识的时候，大多都会被问到“请你说一下自己对于AQS原理的理解”。下面给大家一个示例供大家参加，面试不是背题，大家一定要加入自己的思想，即使加入不了自己的思想也要保证自己能够通俗的讲出来而不是背出来。</p></blockquote><p>下面大部分内容其实在AQS类注释上已经给出了，不过是英语看着比较吃力一点，感兴趣的话可以看看源码。</p><h4 id="AQS-原理概览"><a href="#AQS-原理概览" class="headerlink" title="AQS 原理概览"></a>AQS 原理概览</h4><p><strong>AQS核心思想是，如果被请求的共享资源空闲，则将当前请求资源的线程设置为有效的工作线程，并且将共享资源设置为锁定状态。如果被请求的共享资源被占用，那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制，这个机制AQS是用CLH队列锁实现的，即将暂时获取不到锁的线程加入到队列中。</strong></p><blockquote><p>CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例，仅存在结点之间的关联关系)。AQS是将每条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node)来实现锁的分配。</p></blockquote><p>看个AQS(AbstractQueuedSynchronizer)原理图：</p><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/09/05/174b9170-cf81-11e9-a36e-915d01a5fc8b.png" alt="AQS原理图.png"></p><p>AQS使用一个int成员变量来表示同步状态，通过内置的FIFO队列来完成获取资源线程的排队工作。AQS使用CAS对该同步状态进行原子操作实现对其值的修改。</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">volatile</span> <span class="type">int</span> state;<span class="comment">//共享变量，使用volatile修饰保证线程可见性</span></span><br></pre></td></tr></table></figure><p>状态信息通过protected类型的getState，setState，compareAndSetState进行操作</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><br><span class="line"><span class="comment">//返回同步状态的当前值</span></span><br><span class="line"><span class="keyword">protected</span> <span class="keyword">final</span> <span class="type">int</span> <span class="title function_">getState</span><span class="params">()</span> &#123;  </span><br><span class="line">    <span class="keyword">return</span> state;</span><br><span class="line">&#125;</span><br><span class="line"> <span class="comment">// 设置同步状态的值</span></span><br><span class="line"><span class="keyword">protected</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title function_">setState</span><span class="params">(<span class="type">int</span> newState)</span> &#123; </span><br><span class="line">    state = newState;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//原子地(CAS操作)将同步状态值设置为给定值update如果当前同步状态的值等于expect(期望值)</span></span><br><span class="line"><span class="keyword">protected</span> <span class="keyword">final</span> <span class="type">boolean</span> <span class="title function_">compareAndSetState</span><span class="params">(<span class="type">int</span> expect, <span class="type">int</span> update)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> unsafe.compareAndSwapInt(<span class="built_in">this</span>, stateOffset, expect, update);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="AQS-对资源的共享方式"><a href="#AQS-对资源的共享方式" class="headerlink" title="AQS 对资源的共享方式"></a>AQS 对资源的共享方式</h4><p><strong>AQS定义两种资源共享方式</strong></p><ul><li><strong>Exclusive</strong>(独占)：只有一个线程能执行，如ReentrantLock。又可分为公平锁和非公平锁：<ul><li>公平锁：按照线程在队列中的排队顺序，先到者先拿到锁</li><li>非公平锁：当线程要获取锁时，无视队列顺序直接去抢锁，谁抢到就是谁的</li></ul></li><li><strong>Share</strong>(共享)：多个线程可同时执行，如Semaphore&#x2F;CountDownLatch。Semaphore、CountDownLatch、 CyclicBarrier、ReadWriteLock 我们都会在后面讲到。</li></ul><p>ReentrantReadWriteLock 可以看成是组合式，因为ReentrantReadWriteLock也就是读写锁允许多个线程同时对某一资源进行读。</p><p>不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源 state 的获取与释放方式即可，至于具体线程等待队列的维护(如获取资源失败入队&#x2F;唤醒出队等)，AQS已经在顶层实现好了。</p><h4 id="AQS底层使用了模板方法模式"><a href="#AQS底层使用了模板方法模式" class="headerlink" title="AQS底层使用了模板方法模式"></a>AQS底层使用了模板方法模式</h4><p>同步器的设计是基于模板方法模式的，如果需要自定义同步器一般的方式是这样(模板方法模式很经典的一个应用)：</p><ol><li>使用者继承AbstractQueuedSynchronizer并重写指定的方法。(这些重写方法很简单，无非是对于共享资源state的获取和释放)</li><li>将AQS组合在自定义同步组件的实现中，并调用其模板方法，而这些模板方法会调用使用者重写的方法。</li></ol><p>这和我们以往通过实现接口的方式有很大区别，这是模板方法模式很经典的一个运用。</p><p><strong>AQS使用了模板方法模式，自定义同步器时需要重写下面几个AQS提供的模板方法：</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">isHeldExclusively()<span class="comment">//该线程是否正在独占资源。只有用到condition才需要去实现它。</span></span><br><span class="line">tryAcquire(<span class="type">int</span>)<span class="comment">//独占方式。尝试获取资源，成功则返回true，失败则返回false。</span></span><br><span class="line">tryRelease(<span class="type">int</span>)<span class="comment">//独占方式。尝试释放资源，成功则返回true，失败则返回false。</span></span><br><span class="line">tryAcquireShared(<span class="type">int</span>)<span class="comment">//共享方式。尝试获取资源。负数表示失败；0表示成功，但没有剩余可用资源；正数表示成功，且有剩余资源。</span></span><br><span class="line">tryReleaseShared(<span class="type">int</span>)<span class="comment">//共享方式。尝试释放资源，成功则返回true，失败则返回false。</span></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>默认情况下，每个方法都抛出 <code>UnsupportedOperationException</code>。 这些方法的实现必须是内部线程安全的，并且通常应该简短而不是阻塞。AQS类中的其他方法都是final ，所以无法被其他类使用，只有这几个方法可以被其他类使用。 </p><p>以ReentrantLock为例，state初始化为0，表示未锁定状态。A线程lock()时，会调用tryAcquire()独占该锁并将state+1。此后，其他线程再tryAcquire()时就会失败，直到A线程unlock()到state&#x3D;0(即释放锁)为止，其它线程才有机会获取该锁。当然，释放锁之前，A线程自己是可以重复获取此锁的(state会累加)，这就是可重入的概念。但要注意，获取多少次就要释放多么次，这样才能保证state是能回到零态的。</p><p>再以CountDownLatch以例，任务分为N个子线程去执行，state也初始化为N(注意N要与线程个数一致)。这N个子线程是并行执行的，每个子线程执行完后countDown()一次，state会CAS(Compare and Swap)减1。等到所有子线程都执行完后(即state&#x3D;0)，会unpark()主调用线程，然后主调用线程就会从await()函数返回，继续后余动作。</p><p>一般来说，自定义同步器要么是独占方法，要么是共享方式，他们也只需实现<code>tryAcquire-tryRelease</code>、<code>tryAcquireShared-tryReleaseShared</code>中的一种即可。但AQS也支持自定义同步器同时实现独占和共享两种方式，如<code>ReentrantReadWriteLock</code>。</p><h3 id="AQS-组件总结"><a href="#AQS-组件总结" class="headerlink" title="AQS 组件总结"></a>AQS 组件总结</h3><ul><li><strong>Semaphore(信号量)-允许多个线程同时访问：</strong> synchronized 和 ReentrantLock 都是一次只允许一个线程访问某个资源，Semaphore(信号量)可以指定多个线程同时访问某个资源。</li><li><strong>CountDownLatch (倒计时器)：</strong> CountDownLatch是一个同步工具类，用来协调多个线程之间的同步。这个工具通常用来控制线程等待，它可以让某一个线程等待直到倒计时结束，再开始执行。</li><li><strong>CyclicBarrier(循环栅栏)：</strong> CyclicBarrier 和 CountDownLatch 非常类似，它也可以实现线程间的技术等待，但是它的功能比 CountDownLatch 更加复杂和强大。主要应用场景和 CountDownLatch 类似。CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是，让一组线程到达一个屏障(也可以叫同步点)时被阻塞，直到最后一个线程到达屏障时，屏障才会开门，所有被屏障拦截的线程才会继续干活。CyclicBarrier默认的构造方法是 CyclicBarrier(int parties)，其参数表示屏障拦截的线程数量，每个线程调用await()方法告诉 CyclicBarrier 我已经到达了屏障，然后当前线程被阻塞。</li></ul><h2 id="JDK-提供的并发容器总结"><a href="#JDK-提供的并发容器总结" class="headerlink" title="JDK 提供的并发容器总结"></a>JDK 提供的并发容器总结</h2><p>JDK提供的这些容器大部分在 <code>java.util.concurrent</code> 包中。</p><ul><li><strong>ConcurrentHashMap:</strong> 线程安全的HashMap</li><li><strong>CopyOnWriteArrayList:</strong> 线程安全的List，在读多写少的场合性能非常好，远远好于Vector.</li><li><strong>ConcurrentLinkedQueue:</strong> 高效的并发队列，使用链表实现。可以看做一个线程安全的 LinkedList，这是一个非阻塞队列。</li><li><strong>BlockingQueue:</strong> 这是一个接口，JDK内部通过链表、数组等方式实现了这个接口。表示阻塞队列，非常适合用于作为数据共享的通道。</li><li><strong>ConcurrentSkipListMap:</strong> 跳表的实现。这是一个Map，使用跳表的数据结构进行快速查找。</li></ul><h2 id="ConcurrentHashMap"><a href="#ConcurrentHashMap" class="headerlink" title="ConcurrentHashMap"></a>ConcurrentHashMap</h2><p>我们知道<code>HashMap</code>不是线程安全的，在并发场景下如果要保证一种可行的方式是使用 <code>Collections.synchronizedMap()</code> 方法来包装我们的<code>HashMap</code>。但这是通过使用一个全局的锁来同步不同线程间的并发访问，因此会带来不可忽视的性能问题。</p><p>所以就有了<code>HashMap</code>的线程安全版本<code>ConcurrentHashMap</code>的诞生。在<code>ConcurrentHashMap</code>中，无论是读操作还是写操作都能保证很高的性能：在进行读操作时(几乎)不需要加锁，而在写操作时通过锁分段技术只对所操作的段加锁而不影响客户端对其它段的访问。</p><h2 id="CopyOnWriteArrayList"><a href="#CopyOnWriteArrayList" class="headerlink" title="CopyOnWriteArrayList"></a>CopyOnWriteArrayList</h2><h3 id="CopyOnWriteArrayList-简介"><a href="#CopyOnWriteArrayList-简介" class="headerlink" title="CopyOnWriteArrayList 简介"></a>CopyOnWriteArrayList 简介</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><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CopyOnWriteArrayList</span>&lt;E&gt;</span><br><span class="line"><span class="keyword">extends</span> <span class="title class_">Object</span></span><br><span class="line"><span class="keyword">implements</span> <span class="title class_">List</span>&lt;E&gt;, RandomAccess, Cloneable, Serializable</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>在很多应用场景中，读操作可能会远远大于写操作。由于读操作根本不会修改原有的数据，因此对于每次读取都进行加锁其实是一种资源浪费。我们应该允许多个线程同时访问List的内部数据，毕竟读取操作是安全的。</p><p>这和我们之前在多线程章节讲过 <code>ReentrantReadWriteLock</code> 读写锁的思想非常类似，也就是读读共享、写写互斥、读写互斥、写读互斥。JDK中提供了 <code>CopyOnWriteArrayList</code> 类比相比于在读写锁的思想又更进一步。为了将读取的性能发挥到极致，<code>CopyOnWriteArrayList</code> 读取是完全不用加锁的，并且更厉害的是：写入也不会阻塞读取操作。只有写入和写入之间需要进行同步等待。这样一来，读操作的性能就会大幅度提升。<strong>那它是怎么做的呢？</strong></p><h3 id="CopyOnWriteArrayList-是如何做到的？"><a href="#CopyOnWriteArrayList-是如何做到的？" class="headerlink" title="CopyOnWriteArrayList 是如何做到的？"></a>CopyOnWriteArrayList 是如何做到的？</h3><p> <code>CopyOnWriteArrayList</code> 类的所有可变操作（add，set等等）都是通过创建底层数组的新副本来实现的。当 List 需要被修改的时候，我并不修改原有内容，而是对原有数据进行一次复制，将修改的内容写入副本。写完之后，再将修改完的副本替换原来的数据，这样就可以保证写操作不会影响读操作了。</p><p>从 <code>CopyOnWriteArrayList</code> 的名字就能看出<code>CopyOnWriteArrayList</code> 是满足<code>CopyOnWrite</code> 的ArrayList，所谓<code>CopyOnWrite</code> 也就是说：在计算机，如果你想要对一块内存进行修改时，我们不在原有内存块中进行写操作，而是将内存拷贝一份，在新的内存中进行写操作，写完之后呢，就将指向原来内存指针指向新的内存，原来的内存就可以被回收掉了。</p><h3 id="CopyOnWriteArrayList-读取和写入源码简单分析"><a href="#CopyOnWriteArrayList-读取和写入源码简单分析" class="headerlink" title="CopyOnWriteArrayList 读取和写入源码简单分析"></a>CopyOnWriteArrayList 读取和写入源码简单分析</h3><h4 id="CopyOnWriteArrayList-读取操作的实现"><a href="#CopyOnWriteArrayList-读取操作的实现" class="headerlink" title="CopyOnWriteArrayList 读取操作的实现"></a>CopyOnWriteArrayList 读取操作的实现</h4><p>读取操作没有任何同步控制和锁操作，理由就是内部数组 array 不会发生修改，只会被另外一个 array 替换，因此可以保证数据安全。</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><br><span class="line"><span class="comment">/** The array, accessed only via getArray/setArray. */</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">transient</span> <span class="keyword">volatile</span> Object[] array;</span><br><span class="line"><span class="keyword">public</span> E <span class="title function_">get</span><span class="params">(<span class="type">int</span> index)</span> &#123;</span><br><span class="line"><span class="keyword">return</span> get(getArray(), index);</span><br><span class="line">&#125;</span><br><span class="line"><span class="meta">@SuppressWarnings(&quot;unchecked&quot;)</span></span><br><span class="line"><span class="keyword">private</span> E <span class="title function_">get</span><span class="params">(Object[] a, <span class="type">int</span> index)</span> &#123;</span><br><span class="line"><span class="keyword">return</span> (E) a[index];</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">final</span> Object[] getArray() &#123;</span><br><span class="line"><span class="keyword">return</span> array;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h4 id="CopyOnWriteArrayList-写入操作的实现"><a href="#CopyOnWriteArrayList-写入操作的实现" class="headerlink" title="CopyOnWriteArrayList 写入操作的实现"></a>CopyOnWriteArrayList 写入操作的实现</h4><p>CopyOnWriteArrayList 写入操作 add() 方法在添加集合的时候加了锁，保证了同步，避免了多线程写的时候会 copy 出多个副本出来。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Appends the specified element to the end of this list.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> e element to be appended to this list</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> &#123;<span class="doctag">@code</span> true&#125; (as specified by &#123;<span class="doctag">@link</span> Collection#add&#125;)</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">add</span><span class="params">(E e)</span> &#123;</span><br><span class="line"><span class="keyword">final</span> <span class="type">ReentrantLock</span> <span class="variable">lock</span> <span class="operator">=</span> <span class="built_in">this</span>.lock;</span><br><span class="line">lock.lock();<span class="comment">//加锁</span></span><br><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">Object[] elements = getArray();</span><br><span class="line"><span class="type">int</span> <span class="variable">len</span> <span class="operator">=</span> elements.length;</span><br><span class="line">Object[] newElements = Arrays.copyOf(elements, len + <span class="number">1</span>);<span class="comment">//拷贝新数组</span></span><br><span class="line">newElements[len] = e;</span><br><span class="line">setArray(newElements);</span><br><span class="line"><span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">lock.unlock();<span class="comment">//释放锁</span></span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="ConcurrentLinkedQueue"><a href="#ConcurrentLinkedQueue" class="headerlink" title="ConcurrentLinkedQueue"></a>ConcurrentLinkedQueue</h2><p>Java提供的线程安全的 Queue 可以分为<strong>阻塞队列</strong>和<strong>非阻塞队列</strong>，其中阻塞队列的典型例子是 BlockingQueue，非阻塞队列的典型例子是ConcurrentLinkedQueue，在实际应用中要根据实际需要选用阻塞队列或者非阻塞队列。 <strong>阻塞队列可以通过加锁来实现，非阻塞队列可以通过 CAS 操作实现。</strong></p><p>从名字可以看出，<code>ConcurrentLinkedQueue</code>这个队列使用链表作为其数据结构．ConcurrentLinkedQueue 应该算是在高并发环境中性能最好的队列了。它之所有能有很好的性能，是因为其内部复杂的实现。</p><p>ConcurrentLinkedQueue 内部代码我们就不分析了，大家知道ConcurrentLinkedQueue 主要使用 CAS 非阻塞算法来实现线程安全就好了。</p><p>ConcurrentLinkedQueue 适合在对性能要求相对较高，同时对队列的读写存在多个线程同时进行的场景，即如果对队列加锁的成本较高则适合使用无锁的ConcurrentLinkedQueue来替代。</p><h2 id="BlockingQueue"><a href="#BlockingQueue" class="headerlink" title="BlockingQueue"></a>BlockingQueue</h2><h3 id="BlockingQueue-简单介绍"><a href="#BlockingQueue-简单介绍" class="headerlink" title="BlockingQueue 简单介绍"></a>BlockingQueue 简单介绍</h3><p>上面我们己经提到了 ConcurrentLinkedQueue 作为高性能的非阻塞队列。下面我们要讲到的是阻塞队列——BlockingQueue。阻塞队列（BlockingQueue）被广泛使用在“生产者-消费者”问题中，其原因是BlockingQueue提供了可阻塞的插入和移除的方法。当队列容器已满，生产者线程会被阻塞，直到队列未满；当队列容器为空时，消费者线程会被阻塞，直至队列非空时为止。</p><p>BlockingQueue 是一个接口，继承自 Queue，所以其实现类也可以作为 Queue 的实现来使用，而 Queue 又继承自 Collection 接口。下面是 BlockingQueue 的相关实现类：</p><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/09/05/bf07a1b0-cf81-11e9-a36e-915d01a5fc8b.png" alt="BlockingQueue 的实现类.png"></p><p><strong>下面主要介绍一下:ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue，这三个 BlockingQueue 的实现类。</strong></p><h3 id="ArrayBlockingQueue"><a href="#ArrayBlockingQueue" class="headerlink" title="ArrayBlockingQueue"></a>ArrayBlockingQueue</h3><p><strong>ArrayBlockingQueue</strong> 是 BlockingQueue 接口的有界队列实现类，底层采用<strong>数组</strong>来实现。ArrayBlockingQueue一旦创建，容量不能改变。其并发控制采用可重入锁来控制，不管是插入操作还是读取操作，都需要获取到锁才能进行操作。当队列容量满时，尝试将元素放入队列将导致操作阻塞;尝试从一个空队列中取一个元素也会同样阻塞。</p><p>ArrayBlockingQueue 默认情况下不能保证线程访问队列的公平性，所谓公平性是指严格按照线程等待的绝对时间顺序，即最先等待的线程能够最先访问到 ArrayBlockingQueue。而非公平性则是指访问 ArrayBlockingQueue 的顺序不是遵守严格的时间顺序，有可能存在，当 ArrayBlockingQueue 可以被访问时，长时间阻塞的线程依然无法访问到 ArrayBlockingQueue。如果保证公平性，通常会降低吞吐量。如果需要获得公平性的 ArrayBlockingQueue，可采用如下代码：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> ArrayBlockingQueue&lt;Integer&gt; blockingQueue = <span class="keyword">new</span> <span class="title class_">ArrayBlockingQueue</span>&lt;Integer&gt;(<span class="number">10</span>,<span class="literal">true</span>);</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="LinkedBlockingQueue"><a href="#LinkedBlockingQueue" class="headerlink" title="LinkedBlockingQueue"></a>LinkedBlockingQueue</h3><p><strong>LinkedBlockingQueue</strong> 底层基于<strong>单向链表</strong>实现的阻塞队列，可以当做无界队列也可以当做有界队列来使用，同样满足FIFO的特性，与ArrayBlockingQueue 相比起来具有更高的吞吐量，为了防止 LinkedBlockingQueue 容量迅速增，损耗大量内存。通常在创建LinkedBlockingQueue 对象时，会指定其大小，如果未指定，容量等于Integer.MAX_VALUE。</p><p><strong>相关构造方法:</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><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><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> *某种意义上的无界队列</span></span><br><span class="line"><span class="comment"> * Creates a &#123;<span class="doctag">@code</span> LinkedBlockingQueue&#125; with a capacity of</span></span><br><span class="line"><span class="comment"> * &#123;<span class="doctag">@link</span> Integer#MAX_VALUE&#125;.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="title function_">LinkedBlockingQueue</span><span class="params">()</span> &#123;</span><br><span class="line"><span class="built_in">this</span>(Integer.MAX_VALUE);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> *有界队列</span></span><br><span class="line"><span class="comment"> * Creates a &#123;<span class="doctag">@code</span> LinkedBlockingQueue&#125; with the given (fixed) capacity.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> capacity the capacity of this queue</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@throws</span> IllegalArgumentException if &#123;<span class="doctag">@code</span> capacity&#125; is not greater</span></span><br><span class="line"><span class="comment"> *         than zero</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="title function_">LinkedBlockingQueue</span><span class="params">(<span class="type">int</span> capacity)</span> &#123;</span><br><span class="line"><span class="keyword">if</span> (capacity &lt;= <span class="number">0</span>) <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>();</span><br><span class="line"><span class="built_in">this</span>.capacity = capacity;</span><br><span class="line">last = head = <span class="keyword">new</span> <span class="title class_">Node</span>&lt;E&gt;(<span class="literal">null</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="PriorityBlockingQueue"><a href="#PriorityBlockingQueue" class="headerlink" title="PriorityBlockingQueue"></a>PriorityBlockingQueue</h3><p><strong>PriorityBlockingQueue</strong> 是一个支持优先级的无界阻塞队列。默认情况下元素采用自然顺序进行排序，也可以通过自定义类实现 <code>compareTo()</code> 方法来指定元素排序规则，或者初始化时通过构造器参数 <code>Comparator</code> 来指定排序规则。</p><p>PriorityBlockingQueue 并发控制采用的是 <strong>ReentrantLock</strong>，队列为无界队列（ArrayBlockingQueue 是有界队列，LinkedBlockingQueue 也可以通过在构造函数中传入 capacity 指定队列最大的容量，但是 PriorityBlockingQueue 只能指定初始的队列大小，后面插入元素的时候，<strong>如果空间不够的话会自动扩容</strong>）。</p><p>简单地说，它就是 PriorityQueue 的线程安全版本。不可以插入 null 值，同时，插入队列的对象必须是可比较大小的（comparable），否则报 ClassCastException 异常。它的插入操作 put 方法不会 block，因为它是无界队列（take 方法在队列为空的时候会阻塞）。</p><h2 id="ConcurrentSkipListMap"><a href="#ConcurrentSkipListMap" class="headerlink" title="ConcurrentSkipListMap"></a>ConcurrentSkipListMap</h2><p><strong>为了引出ConcurrentSkipListMap，先带着大家简单理解一下跳表。</strong></p><p>对于一个单链表，即使链表是有序的，如果我们想要在其中查找某个数据，也只能从头到尾遍历链表，这样效率自然就会很低，跳表就不一样了。跳表是一种可以用来快速查找的数据结构，有点类似于平衡树。它们都可以对元素进行快速的查找。但一个重要的区别是：对平衡树的插入和删除往往很可能导致平衡树进行一次全局的调整。而对跳表的插入和删除只需要对整个数据结构的局部进行操作即可。这样带来的好处是：在高并发的情况下，你会需要一个全局锁来保证整个平衡树的线程安全。而对于跳表，你只需要部分锁即可。这样，在高并发环境下，你就可以拥有更好的性能。而就查询的性能而言，跳表的时间复杂度也是 <strong>O(logn)</strong> 所以在并发数据结构中，JDK 使用跳表来实现一个 Map。</p><p>跳表的本质是同时维护了多个链表，并且链表是分层的，</p><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/09/05/d49360f0-cf81-11e9-a36e-915d01a5fc8b.png" alt="2级索引跳表.png"></p><p>最低层的链表维护了跳表内所有的元素，每上面一层链表都是下面一层的子集。</p><p>跳表内的所有链表的元素都是排序的。查找时，可以从顶级链表开始找。一旦发现被查找的元素大于当前链表中的取值，就会转入下一层链表继续找。这也就是说在查找过程中，搜索是跳跃式的。如上图所示，在跳表中查找元素18。</p><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/09/05/e52af7c0-cf81-11e9-a36e-915d01a5fc8b.png" alt="在跳表中查找元素18"></p><p>查找18 的时候原来需要遍历 18 次，现在只需要 7 次即可。针对链表长度比较大的时候，构建索引查找效率的提升就会非常明显。</p><p>从上面很容易看出，<strong>跳表是一种利用空间换时间的算法。</strong></p><p>使用跳表实现Map 和使用哈希算法实现Map的另外一个不同之处是：哈希并不会保存元素的顺序，而跳表内所有的元素都是排序的。因此在对跳表进行遍历时，你会得到一个有序的结果。所以，如果你的应用需要有序性，那么跳表就是你不二的选择。JDK 中实现这一数据结构的类是ConcurrentSkipListMap。</p><h2 id="锁相关问题"><a href="#锁相关问题" class="headerlink" title="锁相关问题"></a>锁相关问题</h2><h3 id="何谓悲观锁与乐观锁"><a href="#何谓悲观锁与乐观锁" class="headerlink" title="何谓悲观锁与乐观锁"></a>何谓悲观锁与乐观锁</h3><blockquote><p>乐观锁对应于生活中乐观的人总是想着事情往好的方向发展，悲观锁对应于生活中悲观的人总是想着事情往坏的方向发展。这两种人各有优缺点，不能不以场景而定说一种人好于另外一种人。</p></blockquote><h4 id="悲观锁"><a href="#悲观锁" class="headerlink" title="悲观锁"></a>悲观锁</h4><p>总是假设最坏的情况，每次去拿数据的时候都认为别人会修改，所以每次在拿数据的时候都会上锁，这样别人想拿这个数据就会阻塞直到它拿到锁（<strong>共享资源每次只给一个线程使用，其它线程阻塞，用完后再把资源转让给其它线程</strong>）。传统的关系型数据库里边就用到了很多这种锁机制，比如行锁，表锁等，读锁，写锁等，都是在做操作之前先上锁。Java中<code>synchronized</code>和<code>ReentrantLock</code>等独占锁就是悲观锁思想的实现。</p><h4 id="乐观锁"><a href="#乐观锁" class="headerlink" title="乐观锁"></a>乐观锁</h4><p>总是假设最好的情况，每次去拿数据的时候都认为别人不会修改，所以不会上锁，但是在更新的时候会判断一下在此期间别人有没有去更新这个数据，可以使用版本号机制和CAS算法实现。<strong>乐观锁适用于多读的应用类型，这样可以提高吞吐量</strong>，像数据库提供的类似于<strong>write_condition机制</strong>，其实都是提供的乐观锁。在Java中<code>java.util.concurrent.atomic</code>包下面的原子变量类就是使用了乐观锁的一种实现方式<strong>CAS</strong>实现的。</p><h4 id="两种锁的使用场景"><a href="#两种锁的使用场景" class="headerlink" title="两种锁的使用场景"></a>两种锁的使用场景</h4><p>从上面对两种锁的介绍，我们知道两种锁各有优缺点，不可认为一种好于另一种，像<strong>乐观锁适用于写比较少的情况下（多读场景）</strong>，即冲突真的很少发生的时候，这样可以省去了锁的开销，加大了系统的整个吞吐量。但如果是多写的情况，一般会经常产生冲突，这就会导致上层应用会不断的进行retry，这样反倒是降低了性能，所以<strong>一般多写的场景下用悲观锁就比较合适。</strong></p><h3 id="乐观锁常见的两种实现方式"><a href="#乐观锁常见的两种实现方式" class="headerlink" title="乐观锁常见的两种实现方式"></a>乐观锁常见的两种实现方式</h3><blockquote><p><strong>乐观锁一般会使用版本号机制或CAS算法实现。</strong></p></blockquote><h4 id="版本号机制"><a href="#版本号机制" class="headerlink" title="版本号机制"></a>版本号机制</h4><p>一般是在数据表中加上一个数据版本号version字段，表示数据被修改的次数，当数据被修改时，version值会加一。当线程A要更新数据值时，在读取数据的同时也会读取version值，在提交更新时，若刚才读取到的version值为当前数据库中的version值相等时才更新，否则重试更新操作，直到更新成功。</p><p><strong>举一个简单的例子：</strong><br>假设数据库中帐户信息表中有一个 version 字段，当前值为 1 ；而当前帐户余额字段（ balance ）为 $100 。</p><ol><li>操作员 A 此时将其读出（ version&#x3D;1 ），并从其帐户余额中扣除 $50（ $100-$50 ）。</li><li>在操作员 A 操作的过程中，操作员B 也读入此用户信息（ version&#x3D;1 ），并从其帐户余额中扣除 $20 （ $100-$20 ）。</li><li>操作员 A 完成了修改工作，将数据版本号加一（ version&#x3D;2 ），连同帐户扣除后余额（ balance&#x3D;$50 ），提交至数据库更新，此时由于提交数据版本大于数据库记录当前版本，数据被更新，数据库记录 version 更新为 2 。</li><li>操作员 B 完成了操作，也将版本号加一（ version&#x3D;2 ）试图向数据库提交数据（ balance&#x3D;$80 ），但此时比对数据库记录版本时发现，操作员 B 提交的数据版本号为 2 ，数据库记录当前版本也为 2 ，不满足 “ 提交版本必须大于记录当前版本才能执行更新 “ 的乐观锁策略，因此，操作员 B 的提交被驳回。</li></ol><p>这样，就避免了操作员 B 用基于 version&#x3D;1 的旧数据修改的结果覆盖操作员A 的操作结果的可能。</p><h4 id="CAS算法"><a href="#CAS算法" class="headerlink" title="CAS算法"></a>CAS算法</h4><p>即<strong>compare and swap（比较与交换）</strong>，是一种有名的<strong>无锁算法</strong>。无锁编程，即不使用锁的情况下实现多线程之间的变量同步，也就是在没有线程被阻塞的情况下实现变量的同步，所以也叫非阻塞同步（Non-blocking Synchronization）。<strong>CAS算法</strong>涉及到三个操作数</p><ul><li>需要读写的内存值 V </li><li>进行比较的值 A </li><li>拟写入的新值 B</li></ul><p>当且仅当 V 的值等于 A时，CAS通过原子方式用新值B来更新V的值，否则不会执行任何操作（比较和替换是一个原子操作）。一般情况下是一个<strong>自旋操作</strong>，即<strong>不断的重试</strong>。</p><h3 id="乐观锁的缺点"><a href="#乐观锁的缺点" class="headerlink" title="乐观锁的缺点"></a>乐观锁的缺点</h3><blockquote><p> ABA 问题是乐观锁一个常见的问题</p></blockquote><h4 id="ABA-问题"><a href="#ABA-问题" class="headerlink" title="ABA 问题"></a>ABA 问题</h4><p>如果一个变量V初次读取的时候是A值，并且在准备赋值的时候检查到它仍然是A值，那我们就能说明它的值没有被其他线程修改过了吗？很明显是不能的，因为在这段时间它的值可能被改为其他值，然后又改回A，那CAS操作就会误认为它从来没有被修改过。这个问题被称为CAS操作的 <strong>“ABA”问题。</strong></p><p>JDK 1.5 以后的 <code>AtomicStampedReference 类</code>就提供了此种能力，其中的 <code>compareAndSet 方法</code>就是首先检查当前引用是否等于预期引用，并且当前标志是否等于预期标志，如果全部相等，则以原子方式将该引用和该标志的值设置为给定的更新值。</p><h4 id="循环时间长开销大"><a href="#循环时间长开销大" class="headerlink" title="循环时间长开销大"></a>循环时间长开销大</h4><p><strong>自旋CAS（也就是不成功就一直循环执行直到成功）如果长时间不成功，会给CPU带来非常大的执行开销。</strong> 如果JVM能支持处理器提供的pause指令那么效率会有一定的提升，pause指令有两个作用，第一它可以延迟流水线执行指令（de-pipeline）,使CPU不会消耗过多的执行资源，延迟的时间取决于具体实现的版本，在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突（memory order violation）而引起CPU流水线被清空（CPU pipeline flush），从而提高CPU的执行效率。</p><h4 id="只能保证一个共享变量的原子操作"><a href="#只能保证一个共享变量的原子操作" class="headerlink" title="只能保证一个共享变量的原子操作"></a>只能保证一个共享变量的原子操作</h4><p>CAS 只对单个共享变量有效，当操作涉及跨多个共享变量时 CAS 无效。但是从 JDK 1.5开始，提供了<code>AtomicReference类</code>来保证引用对象之间的原子性，你可以把多个变量放在一个对象里来进行 CAS 操作.所以我们可以使用锁或者利用<code>AtomicReference类</code>把多个共享变量合并成一个共享变量来操作。</p><h3 id="CAS与synchronized的使用情景"><a href="#CAS与synchronized的使用情景" class="headerlink" title="CAS与synchronized的使用情景"></a>CAS与synchronized的使用情景</h3><blockquote><p><strong>简单的来说CAS适用于写比较少的情况下（多读场景，冲突一般较少），synchronized适用于写比较多的情况下（多写场景，冲突一般较多）</strong></p></blockquote><ol><li>对于资源竞争较少（线程冲突较轻）的情况，使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源；而CAS基于硬件实现，不需要进入内核，不需要切换线程，操作自旋几率较少，因此可以获得更高的性能。</li><li>对于资源竞争严重（线程冲突严重）的情况，CAS自旋的概率会比较大，从而浪费更多的CPU资源，效率低于synchronized。</li></ol><blockquote><p>Java并发编程这个领域中synchronized关键字一直都是元老级的角色，很久之前很多人都会称它为 <strong>“重量级锁”</strong> 。但是，在JavaSE 1.6之后进行了主要包括为了减少获得锁和释放锁带来的性能消耗而引入的 <strong>偏向锁</strong> 和 <strong>轻量级锁</strong> 以及其它<strong>各种优化</strong>之后变得在某些情况下并不是那么重了。synchronized的底层实现主要依靠 <strong>Lock-Free</strong> 的队列，基本思路是 <strong>自旋后阻塞</strong>，<strong>竞争切换后继续竞争锁</strong>，<strong>稍微牺牲了公平性，但获得了高吞吐量</strong>。在线程冲突较少的情况下，可以获得和CAS类似的性能；而线程冲突严重的情况下，性能远高于CAS。</p></blockquote>]]></content>
    
    
    <summary type="html">Java面试、实践等相关</summary>
    
    
    
    <category term="Java" scheme="https://newgr8player.top/categories/Java/"/>
    
    
    <category term="Java" scheme="https://newgr8player.top/tags/Java/"/>
    
    <category term="面试" scheme="https://newgr8player.top/tags/%E9%9D%A2%E8%AF%95/"/>
    
    <category term="实践" scheme="https://newgr8player.top/tags/%E5%AE%9E%E8%B7%B5/"/>
    
  </entry>
  
  <entry>
    <title>Java String 源码分析</title>
    <link href="https://newgr8player.top/posts/Java-String-%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/"/>
    <id>https://newgr8player.top/posts/Java-String-%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/</id>
    <published>2019-08-25T17:35:45.000Z</published>
    <updated>2019-08-26T07:06:50.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="定义"><a href="#定义" class="headerlink" title="定义"></a>定义</h1><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="keyword">public</span> <span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">String</span> <span class="keyword">implements</span> <span class="title class_">java</span>.io.Serializable, Comparable&lt;String&gt;, CharSequence &#123;&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>从定义可以看出</p><ul><li><code>String</code> 被 <code>final</code> 修饰(不允许被继承)</li></ul><p>实现了如下接口：</p><ul><li><code>Serializable</code>（可以序列化和反序列化）</li><li><code>Comparable</code>（可以进行自定义的字符串比较）</li><li>CharSequence（一个可读序列。此接口对许多不同种类的 char 序列提供统一的只读访问。StringBuilder 和StringBuffer也实现了这个接口）</li></ul><h1 id="属性"><a href="#属性" class="headerlink" title="属性"></a>属性</h1><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="comment">/** 用于存放字符串的数组 */</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> <span class="type">char</span> value[];</span><br><span class="line"></span><br></pre></td></tr></table></figure><blockquote><p>由于被final修饰，故String对象一旦初始化后，其即为不可变（指其内容不能被修改，其引用还是可以指向其他的内容）。<br><code>String</code> 是基于 <code>char[]</code> 实现的<br>频繁拼接字符串建议使用StringBuffer、StringBuilder、StringJoiner等</p></blockquote><h1 id="构造方法"><a href="#构造方法" class="headerlink" title="构造方法"></a>构造方法</h1><h2 id="构造方法-1"><a href="#构造方法-1" class="headerlink" title="构造方法"></a>构造方法</h2><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="title function_">String</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="built_in">this</span>.value = <span class="string">&quot;&quot;</span>.value;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><blockquote><p>即 <code>String str = new String();</code> 时，变量<code>str</code>的值为空字符串。</p></blockquote><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="keyword">public</span> <span class="title function_">String</span><span class="params">(String original)</span> &#123;</span><br><span class="line">    <span class="built_in">this</span>.value = original.value;</span><br><span class="line">    <span class="built_in">this</span>.hash = original.hash;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><blockquote><p>这个方法会产生两个字符串对象，分别为参数<code>original</code>与新创建的字符串对象。</p></blockquote><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="title function_">String</span><span class="params">(<span class="type">char</span> value[])</span> &#123;</span><br><span class="line">    <span class="built_in">this</span>.value = Arrays.copyOf(value, value.length);</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><strong>带有offset与count的重载</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="title function_">String</span><span class="params">(<span class="type">char</span> value[], <span class="type">int</span> offset, <span class="type">int</span> count)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (offset &lt; <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">StringIndexOutOfBoundsException</span>(offset);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (count &lt;= <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (count &lt; <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">StringIndexOutOfBoundsException</span>(count);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (offset &lt;= value.length) &#123;</span><br><span class="line">           <span class="built_in">this</span>.value = <span class="string">&quot;&quot;</span>.value;</span><br><span class="line">           <span class="keyword">return</span>;</span><br><span class="line">       &#125;</span><br><span class="line">   &#125;</span><br><span class="line">     <span class="comment">// Note: offset or count might be near -1&gt;&gt;&gt;1.</span></span><br><span class="line">     <span class="keyword">if</span> (offset &gt; value.length - count) &#123;</span><br><span class="line">         <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">StringIndexOutOfBoundsException</span>(offset + count);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="built_in">this</span>.value = Arrays.copyOfRange(value, offset, offset+count);</span><br><span class="line">&#125;</span><br><span class="line"></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></pre></td><td class="code"><pre><span class="line">String(<span class="type">byte</span> bytes[]);</span><br><span class="line">String(<span class="type">byte</span> bytes[], <span class="type">int</span> offset, <span class="type">int</span> length);</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><strong>需要制定字符集的构造方法</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">String(<span class="type">byte</span> bytes[], Charset charset);</span><br><span class="line">String(<span class="type">byte</span> bytes[], String charsetName);</span><br><span class="line">String(<span class="type">byte</span> bytes[], <span class="type">int</span> offset, <span class="type">int</span> length, Charset charset);</span><br><span class="line">String(<span class="type">byte</span> bytes[], <span class="type">int</span> offset, <span class="type">int</span> length, String charsetName);</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="使用StringBuffer或者StringBuider构造"><a href="#使用StringBuffer或者StringBuider构造" class="headerlink" title="使用StringBuffer或者StringBuider构造"></a>使用<code>StringBuffer</code>或者<code>StringBuider</code>构造</h3><blockquote><p>很少用，因为<code>StringBuffer::toString()</code>或者<code>StringBuider::toString()</code>直接就可以生成一个String对象</p></blockquote><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></pre></td><td class="code"><pre><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment"> * Package private constructor which shares value array for speed.</span></span><br><span class="line"><span class="comment"> * this constructor is always expected to be called with share==true.</span></span><br><span class="line"><span class="comment"> * a separate constructor is needed because we already have a public</span></span><br><span class="line"><span class="comment"> * String(char[]) constructor that makes a copy of the given char[].</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line">String(<span class="type">char</span>[] value, <span class="type">boolean</span> share) &#123;</span><br><span class="line">    <span class="comment">// assert share : &quot;unshared not supported&quot;;</span></span><br><span class="line">    <span class="built_in">this</span>.value = value;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><blockquote><p>这个方法由Java7提供，直接饮用value[]的地址，并且<code>share</code>只能为<code>true</code><br>提供这个方法的原因：性能好，节约空间，安全</p></blockquote><h2 id="一般方法"><a href="#一般方法" class="headerlink" title="一般方法"></a>一般方法</h2><h3 id="equals-方法"><a href="#equals-方法" class="headerlink" title="equals()方法"></a>equals()方法</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="keyword">public</span> <span class="type">boolean</span> <span class="title function_">equals</span><span class="params">(Object anObject)</span> &#123;</span><br><span class="line">    <span class="comment">// 判断是否为同一对象</span></span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">this</span> == anObject) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// 判断 anObject 是不是 String 类型</span></span><br><span class="line">    <span class="keyword">if</span> (anObject <span class="keyword">instanceof</span> String) &#123;</span><br><span class="line">        <span class="type">String</span> <span class="variable">anotherString</span> <span class="operator">=</span> (String)anObject;</span><br><span class="line">        <span class="type">int</span> <span class="variable">n</span> <span class="operator">=</span> value.length;</span><br><span class="line">        <span class="comment">// 判断长度是否相等</span></span><br><span class="line">        <span class="keyword">if</span> (n == anotherString.value.length) &#123;</span><br><span class="line">            <span class="type">char</span> v1[] = value;</span><br><span class="line">            <span class="type">char</span> v2[] = anotherString.value;</span><br><span class="line">            <span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line">            <span class="comment">// 循环比较每一个 char 值</span></span><br><span class="line">            <span class="keyword">while</span> (n-- != <span class="number">0</span>) &#123;</span><br><span class="line">                <span class="keyword">if</span> (v1[i] != v2[i])</span><br><span class="line">                    <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">                i++;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><blockquote><p>一般默认是比较是否是同一个对象，此处默认重写，比较是否比较的是内容相同的两个字符串</p><ol><li>比较是否是同一个对象</li><li>比较是否是字符串对象</li><li>循环比较每一位是否相同</li></ol></blockquote><h3 id="hashCode-方法"><a href="#hashCode-方法" class="headerlink" title="hashCode()方法"></a>hashCode()方法</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">hashCode</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">h</span> <span class="operator">=</span> hash;</span><br><span class="line">    <span class="keyword">if</span> (h == <span class="number">0</span> &amp;&amp; value.length &gt; <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="type">char</span> val[] = value;</span><br><span class="line">        <span class="comment">// 数学公式：s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]*31^0</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; value.length; i++) &#123;</span><br><span class="line">            h = <span class="number">31</span> * h + val[i];</span><br><span class="line">        &#125;</span><br><span class="line">        hash = h;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> h;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><blockquote><p>常规hashCode方法</p></blockquote><h3 id="substring-方法"><a href="#substring-方法" class="headerlink" title="substring()方法"></a>substring()方法</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> String <span class="title function_">substring</span><span class="params">(<span class="type">int</span> beginIndex, <span class="type">int</span> endIndex)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (beginIndex &lt; <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">StringIndexOutOfBoundsException</span>(beginIndex);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (endIndex &gt; value.length) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">StringIndexOutOfBoundsException</span>(endIndex);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="type">int</span> <span class="variable">subLen</span> <span class="operator">=</span> endIndex - beginIndex;</span><br><span class="line">    <span class="keyword">if</span> (subLen &lt; <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">StringIndexOutOfBoundsException</span>(subLen);</span><br><span class="line">    &#125;</span><br><span class="line">   <span class="comment">// 将原来的 char[] 中的值逐一复制到新的 String 中</span></span><br><span class="line">    <span class="keyword">return</span> ((beginIndex == <span class="number">0</span>) &amp;&amp; (endIndex == value.length)) ? <span class="built_in">this</span></span><br><span class="line">            : <span class="keyword">new</span> <span class="title class_">String</span>(value, beginIndex, subLen);</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><blockquote><p>Java 7 提供了一种新的实现方式牺牲一些性能，避免内存泄露。 Java 6 时，是同一个数组中操作。Java 7 是创建一个新的数组。</p></blockquote><h3 id="替换方法"><a href="#替换方法" class="headerlink" title="替换方法"></a>替换方法</h3><blockquote><p>replaceFirst、replaceAll、replace区别：<br>replace 的参数是 char 和 CharSequence，即可以支持字符的替换，也支持字符串的替换；<br>replaceAll 和 replaceFirst 的参数是 regex，即基于规则表达式的替换（如果所用的参数据不是基于规则表达式的，则与replace()替换字符串的效果一样。）</p></blockquote><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></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * value 为存储当前字符串对象的字符串数组名</span></span><br><span class="line"><span class="comment"> * 优化了比较次数</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> oldChar 将被替换掉的字符</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> newChar 新字符</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> String <span class="title function_">replace</span><span class="params">(<span class="type">char</span> oldChar, <span class="type">char</span> newChar)</span> &#123;</span><br><span class="line">    <span class="comment">//如果新字符和老字符相同，直接返回当前字符串对象</span></span><br><span class="line">    <span class="keyword">if</span> (oldChar != newChar) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">len</span> <span class="operator">=</span> value.length; <span class="comment">//获取当前字符串长度</span></span><br><span class="line">        <span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> -<span class="number">1</span>;</span><br><span class="line">        <span class="type">char</span>[] val = value; <span class="comment">//目的：为了防止 getfield（获取指定类的实例域，并将其值压入到栈顶）这个操作码的执行</span></span><br><span class="line">        <span class="comment">//目的：找出将被替换的字符(oldChar)所在字符串数组中第一个字符的下标位置</span></span><br><span class="line">        <span class="keyword">while</span> (++i &lt; len) &#123;</span><br><span class="line">            <span class="comment">// 当找到第一个将被替换的字符时，跳出循环；i 就记录了下标位置</span></span><br><span class="line">            <span class="comment">// 如果没有找到，i==len 将不会进入下面的，直接返回当前字符串</span></span><br><span class="line">            <span class="keyword">if</span> (val[i] == oldChar) &#123;</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (i &lt; len) &#123;</span><br><span class="line">            <span class="comment">//将第一个将被替换的字符(oldChar)之前的不需要被替换的字符赋值给新数组buf</span></span><br><span class="line">            <span class="type">char</span> buf[] = <span class="keyword">new</span> <span class="title class_">char</span>[len];</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; i; j++) &#123;</span><br><span class="line">                buf[j] = val[j];</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">// 从第一个将被替换的字符下标(i)开始，对字符进行替换操作</span></span><br><span class="line">            <span class="keyword">while</span> (i &lt; len) &#123;</span><br><span class="line">                <span class="type">char</span> <span class="variable">c</span> <span class="operator">=</span> val[i];</span><br><span class="line">                buf[i] = (c == oldChar) ? newChar : c;</span><br><span class="line">                i++;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">String</span>(buf, <span class="literal">true</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="built_in">this</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">Java8新特性简单介绍</summary>
    
    
    
    <category term="Java" scheme="https://newgr8player.top/categories/Java/"/>
    
    
    <category term="Java" scheme="https://newgr8player.top/tags/Java/"/>
    
    <category term="源码" scheme="https://newgr8player.top/tags/%E6%BA%90%E7%A0%81/"/>
    
  </entry>
  
  <entry>
    <title>Java8新特性概览</title>
    <link href="https://newgr8player.top/posts/Java8%E6%96%B0%E7%89%B9%E6%80%A7%E6%A6%82%E8%A7%88/"/>
    <id>https://newgr8player.top/posts/Java8%E6%96%B0%E7%89%B9%E6%80%A7%E6%A6%82%E8%A7%88/</id>
    <published>2018-09-14T03:04:06.000Z</published>
    <updated>2019-08-26T02:52:26.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="接口的默认实现"><a href="#接口的默认实现" class="headerlink" title="接口的默认实现"></a>接口的默认实现</h1><p>Java8允许我们通过使用<code>default</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> <span class="title class_">Formula</span> &#123;</span><br><span class="line">    <span class="type">double</span> <span class="title function_">calculate</span><span class="params">(<span class="type">int</span> a)</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">default</span> <span class="type">double</span> <span class="title function_">sqrt</span><span class="params">(<span class="type">int</span> a)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> Math.sqrt(a);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>除了抽象方法<code>calculate</code>外，接口<code>Formula</code>也定义了包含默认实现的<code>sqrt</code>方法。实现类只需要实现抽象方法<code>calculate</code>。默认方法<code>sqrt</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></pre></td><td class="code"><pre><span class="line"><span class="type">Formula</span> <span class="variable">formula</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Formula</span>() &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">double</span> <span class="title function_">calculate</span><span class="params">(<span class="type">int</span> a)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> sqrt(a * <span class="number">100</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">formula.calculate(<span class="number">100</span>); <span class="comment">// 100.0</span></span><br><span class="line">formula.sqrt(<span class="number">16</span>); <span class="comment">// 4.0</span></span><br></pre></td></tr></table></figure><p><code>formula</code>被作为匿名对象实现。代码看起来比较冗长，6行代码职位了实现一个简单的计算<code>sqrt(a * 100)</code>。</p><h1 id="Lambda表达式"><a href="#Lambda表达式" class="headerlink" title="Lambda表达式"></a>Lambda表达式</h1><p>首先是一个先前版本中对<code>List&lt;String&gt;</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">List&lt;String&gt; names = Arrays.asList(<span class="string">&quot;peter&quot;</span>, <span class="string">&quot;anna&quot;</span>, <span class="string">&quot;mike&quot;</span>, <span class="string">&quot;xenia&quot;</span>);</span><br><span class="line"></span><br><span class="line">Collections.sort(names, <span class="keyword">new</span> <span class="title class_">Comparator</span>&lt;String&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">int</span> <span class="title function_">compare</span><span class="params">(String a, String b)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> b.compareTo(a);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>静态方法<code>Collections.sort</code>传入了一个<code>list</code>和<code>Comparator</code>对象用于对一直的列表进行排序。<br>为了不在频繁的创建匿名对象，Java8推出了更简短的语法:<code>lambda表达式</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">Collections.sort(names, (String a, String b) -&gt; &#123;</span><br><span class="line">    <span class="keyword">return</span> b.compareTo(a);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>改写成Labda表达式使得代码变得更加直观简短</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">Collections.sort(names, (String a, String b) -&gt; b.compareTo(a));</span><br></pre></td></tr></table></figure><p>对于方法体内只有一行代码的表达式，可以不使用<code>&#123;&#125;</code>和<code>return</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">Collections.sort(names, (a, b) -&gt; b.compareTo(a));</span><br></pre></td></tr></table></figure><p>因为Java编译器会自动推断传入参数的类型，所以参数表中的参数类型也可以省略。</p><h1 id="函数式接口"><a href="#函数式接口" class="headerlink" title="函数式接口"></a>函数式接口</h1><p>lambda表达式如何匹配Java中的类型呢？每个lambda都由一个接口去指定类型。它(函数式接口)必须<code>仅包含一个抽象方法的声明</code>。每个该类型lambda表达式都会匹配这个抽象方法。</p><p>只要接口内只有一个抽象方法我们就可以把它当做lambda表达式使用。但是，为了确保接口是一定满足要求的，应当添加<code>@FunctionalInterface</code>注解。添加该注解后，当你再想接口内声明第二个抽象方法的时候，编译器会直接返回一个编译错误。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@FunctionalInterface</span></span><br><span class="line"><span class="keyword">interface</span> <span class="title class_">Converter</span>&lt;F, T&gt; &#123;</span><br><span class="line">    T <span class="title function_">convert</span><span class="params">(F from)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>请注意，此处去掉<code>@FunctionalInterface</code>注解后该接口仍然可以作为lambda表达式使用。</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Converter&lt;String, Integer&gt; converter = (from) -&gt; Integer.valueOf(from);</span><br><span class="line"><span class="type">Integer</span> <span class="variable">converted</span> <span class="operator">=</span> converter.convert(<span class="string">&quot;123&quot;</span>);</span><br><span class="line">System.out.println(converted); <span class="comment">// 123</span></span><br></pre></td></tr></table></figure><blockquote><p>一个或多个<code>static</code>方法不会影响该接口成为函数式接口<br>接口中可以包含被<code>default</code>关键字修饰的默认实现<br>支持泛型与继承关系，只要继承后的接口仍然满足成为函数式接口的条件</p></blockquote><h1 id="方法与构造方法的引用"><a href="#方法与构造方法的引用" class="headerlink" title="方法与构造方法的引用"></a>方法与构造方法的引用</h1><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">Converter&lt;String, Integer&gt; converter = Integer::valueOf;</span><br><span class="line"><span class="type">Integer</span> <span class="variable">converted</span> <span class="operator">=</span> converter.convert(<span class="string">&quot;123&quot;</span>);</span><br><span class="line">System.out.println(converted); <span class="comment">// 123</span></span><br></pre></td></tr></table></figure><p>Java8可以使用<code>::</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Something</span> &#123;</span><br><span class="line">    String <span class="title function_">startsWith</span><span class="params">(String s)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> String.valueOf(s.charAt(<span class="number">0</span>));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Something</span> <span class="variable">something</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Something</span>();</span><br><span class="line">Converter&lt;String, String&gt; converter = something::startsWith;</span><br><span class="line"><span class="type">String</span> <span class="variable">converted</span> <span class="operator">=</span> converter.convert(<span class="string">&quot;Java&quot;</span>);</span><br><span class="line">System.out.println(converted); <span class="comment">// &quot;J&quot;</span></span><br></pre></td></tr></table></figure><p>接下来演示调用构造方法的情况：</p><ol><li><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Person</span> &#123;</span><br><span class="line">    String firstName;</span><br><span class="line">    String lastName;</span><br><span class="line"></span><br><span class="line">    Person() &#123;&#125;</span><br><span class="line"></span><br><span class="line">    Person(String firstName, String lastName) &#123;</span><br><span class="line">        <span class="built_in">this</span>.firstName = firstName;</span><br><span class="line">        <span class="built_in">this</span>.lastName = lastName;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>然后创建一个Person类型的工厂的接口用来创建person对象</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">interface</span> <span class="title class_">PersonFactory</span>&lt;P <span class="keyword">extends</span> <span class="title class_">Person</span>&gt; &#123;</span><br><span class="line">    P <span class="title function_">create</span><span class="params">(String firstName, String lastName)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><p>使用示例<br>我们可以通过构造方法的引用去调用该接口而不用去实现其定义的抽象方法。</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">PersonFactory&lt;Person&gt; personFactory = Person::<span class="keyword">new</span>;</span><br><span class="line"><span class="type">Person</span> <span class="variable">person</span> <span class="operator">=</span> personFactory.create(<span class="string">&quot;Peter&quot;</span>, <span class="string">&quot;Parker&quot;</span>);</span><br></pre></td></tr></table></figure><p>我们通过<code>Person::new</code>创建了对<code>Person</code>类构造方法的引用。Java编译器会根据<code>PersonFactory.create</code>中传入的参数重载构造方法。</p></li></ol><h1 id="Lambda作用域"><a href="#Lambda作用域" class="headerlink" title="Lambda作用域"></a>Lambda作用域</h1><p>Lambda在访问外层作用域的变量时，与匿名对象的方式类似。</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><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">final</span> <span class="type">int</span> <span class="variable">num</span> <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line">Converter&lt;Integer, String&gt; stringConverter =</span><br><span class="line">(from) -&gt; String.valueOf(from + num);</span><br><span class="line"></span><br><span class="line">stringConverter.convert(<span class="number">2</span>); <span class="comment">// 3</span></span><br></pre></td></tr></table></figure><p>与匿名对象不同，变量<code>num</code>不是必须使用<code>final</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></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="variable">num</span> <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line">Converter&lt;Integer, String&gt; stringConverter =</span><br><span class="line">(from) -&gt; String.valueOf(from + num);</span><br><span class="line"></span><br><span class="line">stringConverter.convert(<span class="number">2</span>); <span class="comment">// 3</span></span><br></pre></td></tr></table></figure><p>但是在编译过程中<code>num</code>会隐式转换为<code>被final修饰</code>的，所以如下代码不能通过编译:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="variable">num</span> <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line">Converter&lt;Integer, String&gt; stringConverter =</span><br><span class="line">(from) -&gt; String.valueOf(from + num);</span><br><span class="line">num = <span class="number">3</span>; <span class="comment">/* 这里不同 */</span></span><br></pre></td></tr></table></figure><p>并且在labda表达式中修改<code>num</code>的值也是<strong>被禁止</strong>的。</p><h2 id="访问属性与静态变量"><a href="#访问属性与静态变量" class="headerlink" title="访问属性与静态变量"></a>访问属性与静态变量</h2><p>与匿名对象相同，在lambda作用域内，可以对外部的属性与静态变量进行访问与修改。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Lambda4</span> &#123;</span><br><span class="line">    <span class="keyword">static</span> <span class="type">int</span> outerStaticNum;</span><br><span class="line">    <span class="type">int</span> outerNum;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">testScopes</span><span class="params">()</span> &#123;</span><br><span class="line">        Converter&lt;Integer, String&gt; stringConverter1 = (from) -&gt; &#123;</span><br><span class="line">        outerNum = <span class="number">23</span>;</span><br><span class="line">        <span class="keyword">return</span> String.valueOf(from);</span><br><span class="line">    &#125;;</span><br><span class="line"></span><br><span class="line">    Converter&lt;Integer, String&gt; stringConverter2 = (from) -&gt; &#123;</span><br><span class="line">        outerStaticNum = <span class="number">72</span>;</span><br><span class="line">        <span class="keyword">return</span> String.valueOf(from);</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><h2 id="访问接口的默认实现方法"><a href="#访问接口的默认实现方法" class="headerlink" title="访问接口的默认实现方法"></a>访问接口的默认实现方法</h2><p>在之前提到的<code>Formula</code>接口中声明的被<code>default</code>修饰的的<code>sqrt</code>方法可以直接使用。但是对于lambda表达式则不可以。</p><p>默认方法在lambda作用域中<strong>不能被访问</strong>，以下代码不能正常运行。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Formula</span> <span class="variable">formula</span> <span class="operator">=</span> (a) -&gt; sqrt( a * <span class="number">100</span>);</span><br></pre></td></tr></table></figure><p>内建函数式接口</p><p>JDK1.8中包括了许多内建的函数式接口。有些源自早先版本的JDK比如<code>Comparator</code>或者<code>Runnable</code>。这些现有的接口都是使用<code>@FunctionalInterface</code>注解拓展用来支持Lambda表达式。</p><p>Java8同样也加入了一些新的函数式接口使得开发更加快捷。比如<a href="https://code.google.com/p/guava-libraries/">Google Guava</a>库。如果经常使用这个库，建议查看它的源码，以便了解其中较为常用的方法是如何通过函数式接口进行拓展的。</p><h1 id="断言接口-Predicates"><a href="#断言接口-Predicates" class="headerlink" title="断言接口(Predicates)"></a>断言接口(Predicates)</h1><p><code>Predicates</code>是一个仅传入一个参数返回<code>boolean</code>类型的方法。这个接口包含了许多默认方法用于判断复杂逻辑(<code>且</code>，<code>或</code>，<code>非</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></pre></td><td class="code"><pre><span class="line">Predicate&lt;String&gt; predicate = (s) -&gt; s.length() &gt; <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">predicate.test(<span class="string">&quot;foo&quot;</span>); <span class="comment">// true</span></span><br><span class="line">predicate.negate().test(<span class="string">&quot;foo&quot;</span>); <span class="comment">// false</span></span><br><span class="line"></span><br><span class="line">Predicate&lt;Boolean&gt; nonNull = Objects::nonNull;</span><br><span class="line">Predicate&lt;Boolean&gt; isNull = Objects::isNull;</span><br><span class="line"></span><br><span class="line">Predicate&lt;String&gt; isEmpty = String::isEmpty;</span><br><span class="line">Predicate&lt;String&gt; isNotEmpty = isEmpty.negate();</span><br></pre></td></tr></table></figure><p>Functions接口(Functions)</p><p>Functions接口传入一个参数并且有返回值。默认方法可以和链式方法一起使用(<code>compose</code>,<code>andThen</code>)</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Function&lt;String, Integer&gt; toInteger = Integer::valueOf;</span><br><span class="line">Function&lt;String, String&gt; backToString = toInteger.andThen(String::valueOf);</span><br><span class="line"></span><br><span class="line">backToString.apply(<span class="string">&quot;123&quot;</span>); <span class="comment">// &quot;123&quot;</span></span><br></pre></td></tr></table></figure><p>生产者(Suppliers)</p><p>通过生产者能得到一个传入泛型的对象。与Functions接口不同，生产者不传入任何参数。</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">Supplier&lt;Person&gt; personSupplier = Person::<span class="keyword">new</span>;</span><br><span class="line">personSupplier.get(); <span class="comment">// new Person</span></span><br></pre></td></tr></table></figure><p>消费者(Consumers)</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></pre></td><td class="code"><pre><span class="line">Consumer&lt;Person&gt; greeter = (p) -&gt; System.out.println(<span class="string">&quot;Hello, &quot;</span> + p.firstName);</span><br><span class="line">greeter.accept(<span class="keyword">new</span> <span class="title class_">Person</span>(<span class="string">&quot;Luke&quot;</span>, <span class="string">&quot;Skywalker&quot;</span>));</span><br></pre></td></tr></table></figure><h1 id="比较器-Comparators"><a href="#比较器-Comparators" class="headerlink" title="比较器(Comparators)"></a>比较器(Comparators)</h1><p><code>Comparators</code>在早先的Java版本中比较常用。Java8为他在接口中添加了大量的默认方法。</p><h1 id="Optionals类-Optionals"><a href="#Optionals类-Optionals" class="headerlink" title="Optionals类(Optionals)"></a>Optionals类(Optionals)</h1><p><code>Optionals</code>不是一个函数式接口，是一种为了避免<code>NullPointerException</code>的实用解决方案。</p><p><code>Optionals</code>是一个简单的值容器，它可以为<code>null</code>或<code>非空值</code>。如果一个方法可能返回<code>null</code>也可能返回<code>非空值</code>就可以使用<code>Optional</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></pre></td><td class="code"><pre><span class="line">Optional&lt;String&gt; optional = Optional.of(<span class="string">&quot;bam&quot;</span>);</span><br><span class="line"></span><br><span class="line">optional.isPresent(); <span class="comment">// true</span></span><br><span class="line">optional.get(); <span class="comment">// &quot;bam&quot;</span></span><br><span class="line">optional.orElse(<span class="string">&quot;fallback&quot;</span>); <span class="comment">// &quot;bam&quot;</span></span><br><span class="line"></span><br><span class="line">optional.ifPresent((s) -&gt; System.out.println(s.charAt(<span class="number">0</span>))); <span class="comment">// &quot;b&quot;</span></span><br></pre></td></tr></table></figure><h1 id="流-Streams"><a href="#流-Streams" class="headerlink" title="流(Streams)"></a>流(Streams)</h1><p><code>java.util.Stream</code>提供对一组元素进行一个或多个操作的方法。流操作不是<code>intermediate</code>(可以继续操作)就是<code>terminal</code>(不可以继续操作)的。<code>terminal</code>操作返回一个明确的类型结果，<code>intermediate</code>操作返回流本书以便于继续调用Stream方法进行处理。流处理的数据源可以来自<code>java.util.Collection</code>中的列表(list)或者集合<code>set</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></pre></td><td class="code"><pre><span class="line">List&lt;String&gt; stringCollection = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;();</span><br><span class="line">stringCollection.add(<span class="string">&quot;ddd2&quot;</span>);</span><br><span class="line">stringCollection.add(<span class="string">&quot;aaa2&quot;</span>);</span><br><span class="line">stringCollection.add(<span class="string">&quot;bbb1&quot;</span>);</span><br><span class="line">stringCollection.add(<span class="string">&quot;aaa1&quot;</span>);</span><br><span class="line">stringCollection.add(<span class="string">&quot;bbb3&quot;</span>);</span><br><span class="line">stringCollection.add(<span class="string">&quot;ccc&quot;</span>);</span><br><span class="line">stringCollection.add(<span class="string">&quot;bbb2&quot;</span>);</span><br><span class="line">stringCollection.add(<span class="string">&quot;ddd1&quot;</span>);</span><br></pre></td></tr></table></figure><p>在Java8中Collections已被拓展用于支持流操作，所以只需要调用<code>Collection.stream()</code>或<code>Collection.parallelStream()</code>就能得到流处理所需要的数据源。</p><h2 id="过滤方法-Filter"><a href="#过滤方法-Filter" class="headerlink" title="过滤方法(Filter)"></a>过滤方法(Filter)</h2><p>Filter传入一个<code>Predicate</code>对象用来过滤流中的所有元素。这个操作是<code>intermediate</code>(可继续操作的)。所以我们在调用filter后还可以继续调用<code>forEach</code>去遍历流中的元素。但是<code>forEach</code>是<code>terminal</code>(不可继续操作的)。它的返回值类型是<code>void</code>，所以不能再继续调用任何流处理方法。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">stringCollection</span><br><span class="line">    .stream()</span><br><span class="line">    .filter((s) -&gt; s.startsWith(<span class="string">&quot;a&quot;</span>))</span><br><span class="line">    .forEach(System.out::println);</span><br><span class="line"></span><br><span class="line"><span class="comment">// &quot;aaa2&quot;, &quot;aaa1&quot;</span></span><br></pre></td></tr></table></figure><h2 id="排序方法-Sorted"><a href="#排序方法-Sorted" class="headerlink" title="排序方法(Sorted)"></a>排序方法(Sorted)</h2><p>Sorted是<code>intermediate</code>(可继续操作的)返回按照既定规则排序的流数据视图的方法。视图中的元素会按照<code>自然序</code>排序除非指定了<code>Comparator</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></pre></td><td class="code"><pre><span class="line">stringCollection</span><br><span class="line">    .stream()</span><br><span class="line">    .sorted()</span><br><span class="line">    .filter((s) -&gt; s.startsWith(<span class="string">&quot;a&quot;</span>))</span><br><span class="line">    .forEach(System.out::println);</span><br><span class="line"></span><br><span class="line"><span class="comment">// &quot;aaa1&quot;, &quot;aaa2&quot;</span></span><br></pre></td></tr></table></figure><p><strong><code>Sorted</code>仅创建了排序后元素的视图，并没有真正去更改集合中元素的位置，集合中元素位置不会受到该方法的影响</strong></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">System.out.println(stringCollection);</span><br><span class="line"><span class="comment">// ddd2, aaa2, bbb1, aaa1, bbb3, ccc, bbb2, ddd1</span></span><br></pre></td></tr></table></figure><h2 id="映射方法-Map"><a href="#映射方法-Map" class="headerlink" title="映射方法(Map)"></a>映射方法(Map)</h2><p>Map是<code>intermediate</code>(可继续操作的)。它通过给定方法将元素放到另一个对象中。下面的例子把流中的元素转换为大写，当然也可以使用map方法把流中对象转换为另一个类型的对象。结果流的泛型类型取决于传递给map方法的泛型类型。</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">stringCollection</span><br><span class="line">    .stream()</span><br><span class="line">    .map(String::toUpperCase)</span><br><span class="line">    .sorted((a, b) -&gt; b.compareTo(a))</span><br><span class="line">    .forEach(System.out::println);</span><br><span class="line"></span><br><span class="line"><span class="comment">// &quot;DDD2&quot;, &quot;DDD1&quot;, &quot;CCC&quot;, &quot;BBB3&quot;, &quot;BBB2&quot;, &quot;AAA2&quot;, &quot;AAA1&quot;</span></span><br></pre></td></tr></table></figure><h2 id="匹配方法-Match"><a href="#匹配方法-Match" class="headerlink" title="匹配方法(Match)"></a>匹配方法(Match)</h2><p><code>Match</code>是<code>terminal</code>(可继续操作的)。Match可以用来检查流中是否存在复合匹配条件的数据，并返回<code>true</code>或<code>false</code>。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">boolean</span> <span class="variable">anyStartsWithA</span> <span class="operator">=</span></span><br><span class="line">stringCollection</span><br><span class="line">    .stream()</span><br><span class="line">    .anyMatch((s) -&gt; s.startsWith(<span class="string">&quot;a&quot;</span>));</span><br><span class="line"></span><br><span class="line">System.out.println(anyStartsWithA); <span class="comment">// true</span></span><br><span class="line"></span><br><span class="line"><span class="type">boolean</span> <span class="variable">allStartsWithA</span> <span class="operator">=</span> </span><br><span class="line">    stringCollection </span><br><span class="line">        .stream() </span><br><span class="line">        .allMatch((s) -&gt; s.startsWith(<span class="string">&quot;a&quot;</span>));</span><br><span class="line"></span><br><span class="line">System.out.println(allStartsWithA); <span class="comment">// false</span></span><br><span class="line"></span><br><span class="line"><span class="type">boolean</span> <span class="variable">noneStartsWithZ</span> <span class="operator">=</span></span><br><span class="line">    stringCollection</span><br><span class="line">        .stream()</span><br><span class="line">        .noneMatch((s) -&gt; s.startsWith(<span class="string">&quot;z&quot;</span>));</span><br><span class="line"></span><br><span class="line">System.out.println(noneStartsWithZ); <span class="comment">// true</span></span><br></pre></td></tr></table></figure><h2 id="计数器-Count"><a href="#计数器-Count" class="headerlink" title="计数器(Count)"></a>计数器(Count)</h2><p><code>Count</code>是<code>terminal</code>(不可继续操作的)。返回流中元素的数量，返回值类型为<code>long</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></pre></td><td class="code"><pre><span class="line"><span class="type">long</span> <span class="variable">startsWithB</span> <span class="operator">=</span></span><br><span class="line">    stringCollection</span><br><span class="line">        .stream()</span><br><span class="line">        .filter((s) -&gt; s.startsWith(<span class="string">&quot;b&quot;</span>))</span><br><span class="line">        .count();</span><br><span class="line"></span><br><span class="line">System.out.println(startsWithB); <span class="comment">// 3</span></span><br></pre></td></tr></table></figure><h2 id="合并-Reduce"><a href="#合并-Reduce" class="headerlink" title="合并(Reduce)"></a>合并(Reduce)</h2><p><code>Reduce</code>是<code>terminal</code>(不可继续操作的)。它按照给定的规则合并流内的元素，并且使用<code>Optional</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">Optional&lt;String&gt; reduced =</span><br><span class="line">    stringCollection</span><br><span class="line">        .stream()</span><br><span class="line">        .sorted()</span><br><span class="line">        .reduce((s1, s2) -&gt; s1 + <span class="string">&quot;#&quot;</span> + s2);</span><br><span class="line"></span><br><span class="line">reduced.ifPresent(System.out::println);</span><br><span class="line"><span class="comment">// &quot;aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2&quot;</span></span><br></pre></td></tr></table></figure><h2 id="并行流-Parallel-Streams"><a href="#并行流-Parallel-Streams" class="headerlink" title="并行流(Parallel Streams)"></a>并行流(Parallel Streams)</h2><p>流可以是<code>sequential</code>(顺序的)也可以是<code>parallel</code>(并行的)。并行流是在多线程的基础上完成的。</p><p>下面的例子是展示使用并行流并且并行流的使用非常简单。</p><ol><li>首先创建一个有序的并有很多元素的list</li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="variable">max</span> <span class="operator">=</span> <span class="number">1000000</span>;</span><br><span class="line">List&lt;String&gt; values = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;&gt;(max);</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; i++) &#123;</span><br><span class="line">    <span class="type">UUID</span> <span class="variable">uuid</span> <span class="operator">=</span> UUID.randomUUID();</span><br><span class="line">    values.add(uuid.toString());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>然后我们来测试一下对这个list排序所消耗的时间。</p></blockquote><p><strong>顺序执行排序(Sequential Sort)</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">long</span> <span class="variable">t0</span> <span class="operator">=</span> System.nanoTime();</span><br><span class="line"></span><br><span class="line"><span class="type">long</span> <span class="variable">count</span> <span class="operator">=</span> values.stream().sorted().count();</span><br><span class="line">System.out.println(count);</span><br><span class="line"></span><br><span class="line"><span class="type">long</span> <span class="variable">t1</span> <span class="operator">=</span> System.nanoTime();</span><br><span class="line"></span><br><span class="line"><span class="type">long</span> <span class="variable">millis</span> <span class="operator">=</span> TimeUnit.NANOSECONDS.toMillis(t1 - t0);</span><br><span class="line">System.out.println(String.format(<span class="string">&quot;sequential sort took: %d ms&quot;</span>, millis));</span><br><span class="line"></span><br><span class="line"><span class="comment">// sequential sort took: 899 ms</span></span><br></pre></td></tr></table></figure><p><strong>并行执行排序(Parallel Sort)</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">long</span> <span class="variable">t0</span> <span class="operator">=</span> System.nanoTime();</span><br><span class="line"></span><br><span class="line"><span class="type">long</span> <span class="variable">count</span> <span class="operator">=</span> values.parallelStream().sorted().count();</span><br><span class="line">System.out.println(count);</span><br><span class="line"></span><br><span class="line"><span class="type">long</span> <span class="variable">t1</span> <span class="operator">=</span> System.nanoTime();</span><br><span class="line"></span><br><span class="line"><span class="type">long</span> <span class="variable">millis</span> <span class="operator">=</span> TimeUnit.NANOSECONDS.toMillis(t1 - t0);</span><br><span class="line">System.out.println(String.format(<span class="string">&quot;parallel sort took: %d ms&quot;</span>, millis));</span><br><span class="line"></span><br><span class="line"><span class="comment">// parallel sort took: 472 ms</span></span><br></pre></td></tr></table></figure><blockquote><p>根据演示可以很直观的看到，代码量上，两种方式几乎一样，但是并行执行的效率比顺序执行的效率要高出近50%，而在编码过程中需要做的仅仅是将<code>stream()</code>替换为<code>parallelStream()</code>。</p></blockquote><h1 id="Map类-Map"><a href="#Map类-Map" class="headerlink" title="Map类(Map)"></a>Map类(Map)</h1><p>虽然Map现在并不支持流操作，但是它也新加入了许多好用的方法去完成一些常用需求。</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">Map&lt;Integer, String&gt; map = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">10</span>; i++) &#123;</span><br><span class="line">    map.putIfAbsent(i, <span class="string">&quot;val&quot;</span> + i);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">map.forEach((id, val) -&gt; System.out.println(val));</span><br></pre></td></tr></table></figure><p>上面代码中的<code>putIfAbsent</code>避免了我们在进行新增操作时候的非空校验。<code>forEach</code>则传入了一个消费者去操作map中的每个值。</p><p>下面的例子将展示如何通过函数化接口操作map</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">map.computeIfPresent(<span class="number">3</span>, (num, val) -&gt; val + num);</span><br><span class="line">map.get(<span class="number">3</span>); <span class="comment">// val33</span></span><br><span class="line"></span><br><span class="line">map.computeIfPresent(<span class="number">9</span>, (num, val) -&gt; <span class="literal">null</span>);</span><br><span class="line">map.containsKey(<span class="number">9</span>); <span class="comment">// false</span></span><br><span class="line"></span><br><span class="line">map.computeIfAbsent(<span class="number">23</span>, num -&gt; <span class="string">&quot;val&quot;</span> + num);</span><br><span class="line">map.containsKey(<span class="number">23</span>); <span class="comment">// true</span></span><br><span class="line"></span><br><span class="line">map.computeIfAbsent(<span class="number">3</span>, num -&gt; <span class="string">&quot;bam&quot;</span>);</span><br><span class="line">map.get(<span class="number">3</span>); <span class="comment">// val33</span></span><br></pre></td></tr></table></figure><p>下面例子展示在给定key并且当前map内的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></pre></td><td class="code"><pre><span class="line">map.remove(<span class="number">3</span>, <span class="string">&quot;val3&quot;</span>);</span><br><span class="line">map.get(<span class="number">3</span>); <span class="comment">// val33</span></span><br><span class="line"></span><br><span class="line">map.remove(<span class="number">3</span>, <span class="string">&quot;val33&quot;</span>);</span><br><span class="line">map.get(<span class="number">3</span>); <span class="comment">// null</span></span><br></pre></td></tr></table></figure><p>获取给定key内的值，当不存在时，返回给定的默认值：</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">map.getOrDefault(<span class="number">42</span>, <span class="string">&quot;not found&quot;</span>); <span class="comment">// not found</span></span><br></pre></td></tr></table></figure><p>合并merging</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">map.merge(<span class="number">9</span>, <span class="string">&quot;val9&quot;</span>, (value, newValue) -&gt; value.concat(newValue));</span><br><span class="line">map.get(<span class="number">9</span>); <span class="comment">// val9</span></span><br><span class="line"></span><br><span class="line">map.merge(<span class="number">9</span>, <span class="string">&quot;concat&quot;</span>, (value, newValue) -&gt; value.concat(newValue));</span><br><span class="line">map.get(<span class="number">9</span>); <span class="comment">// val9concat</span></span><br></pre></td></tr></table></figure><blockquote><p>如果合并key不冲突则直接合并，否则调用合并方法。</p></blockquote><h1 id="日期API-Date-API"><a href="#日期API-Date-API" class="headerlink" title="日期API(Date API)"></a>日期API(Date API)</h1><p>Java8在<code>java.time</code>包下包含全新的日期和时间API。新的日期API可以与<code>Joda-time</code>库进行比较运算，但是它们并不相同。下面的示例介绍了这个新API最重要的部分。</p><h2 id="时钟类-Clock"><a href="#时钟类-Clock" class="headerlink" title="时钟类(Clock)"></a>时钟类(Clock)</h2><p>时钟类提供对当前日期和时间的访问。时钟类依赖<code>时区</code>，可以使用它代替<code>System.currentTimeMillis()</code>来检索当前毫秒。时间线上的这样一个瞬时点也由<code>Instant</code>类表示.实例可以用来创建原有的<code>java.util.Date</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></pre></td><td class="code"><pre><span class="line"><span class="type">Clock</span> <span class="variable">clock</span> <span class="operator">=</span> Clock.systemDefaultZone();</span><br><span class="line"><span class="type">long</span> <span class="variable">millis</span> <span class="operator">=</span> clock.millis();</span><br><span class="line"></span><br><span class="line"><span class="type">Instant</span> <span class="variable">instant</span> <span class="operator">=</span> clock.instant();</span><br><span class="line"><span class="type">Date</span> <span class="variable">legacyDate</span> <span class="operator">=</span> Date.from(instant);   <span class="comment">// legacy java.util.Date</span></span><br></pre></td></tr></table></figure><h2 id="时区类-Timezones"><a href="#时区类-Timezones" class="headerlink" title="时区类(Timezones)"></a>时区类(Timezones)</h2><p>时区由<code>ZoneId</code>表示。可以通过静态工厂方法访问。时区定义了非常重要的<code>offsets</code>(偏移量)使得它能够在 <code>instants</code>、<code>local dates</code> 与 <code>times</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></pre></td><td class="code"><pre><span class="line">System.out.println(ZoneId.getAvailableZoneIds());</span><br><span class="line"><span class="comment">// prints all available timezone ids</span></span><br><span class="line"></span><br><span class="line"><span class="type">ZoneId</span> <span class="variable">zone1</span> <span class="operator">=</span> ZoneId.of(<span class="string">&quot;Europe/Berlin&quot;</span>);</span><br><span class="line"><span class="type">ZoneId</span> <span class="variable">zone2</span> <span class="operator">=</span> ZoneId.of(<span class="string">&quot;Brazil/East&quot;</span>);</span><br><span class="line">System.out.println(zone1.getRules());</span><br><span class="line">System.out.println(zone2.getRules());</span><br><span class="line"></span><br><span class="line"><span class="comment">// ZoneRules[currentStandardOffset=+01:00]</span></span><br><span class="line"><span class="comment">// ZoneRules[currentStandardOffset=-03:00]</span></span><br></pre></td></tr></table></figure><h2 id="本地时间类-LocalTime"><a href="#本地时间类-LocalTime" class="headerlink" title="本地时间类(LocalTime)"></a>本地时间类(LocalTime)</h2><p>本地时间类不使用时区表示(指显示不包含时区)，例如：<code>10pm</code>或者<code>17:30:15</code>。下面的例子使用上面定义的时区创建了两个<code>LocalTime</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></pre></td><td class="code"><pre><span class="line"><span class="type">LocalTime</span> <span class="variable">now1</span> <span class="operator">=</span> LocalTime.now(zone1);</span><br><span class="line"><span class="type">LocalTime</span> <span class="variable">now2</span> <span class="operator">=</span> LocalTime.now(zone2);</span><br><span class="line"></span><br><span class="line">System.out.println(now1.isBefore(now2));  <span class="comment">// false</span></span><br><span class="line"></span><br><span class="line"><span class="type">long</span> <span class="variable">hoursBetween</span> <span class="operator">=</span> ChronoUnit.HOURS.between(now1, now2);</span><br><span class="line"><span class="type">long</span> <span class="variable">minutesBetween</span> <span class="operator">=</span> ChronoUnit.MINUTES.between(now1, now2);</span><br><span class="line"></span><br><span class="line">System.out.println(hoursBetween);       <span class="comment">// -3</span></span><br><span class="line">System.out.println(minutesBetween);     <span class="comment">// -239</span></span><br></pre></td></tr></table></figure><blockquote><p><code>LocalTime</code>可以使用很多工厂去简化实例的新建过程，包括转化成字符串。</p></blockquote><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">LocalTime</span> <span class="variable">late</span> <span class="operator">=</span> LocalTime.of(<span class="number">23</span>, <span class="number">59</span>, <span class="number">59</span>);</span><br><span class="line">System.out.println(late);       <span class="comment">// 23:59:59</span></span><br><span class="line"></span><br><span class="line"><span class="type">DateTimeFormatter</span> <span class="variable">germanFormatter</span> <span class="operator">=</span></span><br><span class="line">    DateTimeFormatter</span><br><span class="line">        .ofLocalizedTime(FormatStyle.SHORT)</span><br><span class="line">        .withLocale(Locale.GERMAN);</span><br><span class="line"></span><br><span class="line"><span class="type">LocalTime</span> <span class="variable">leetTime</span> <span class="operator">=</span> LocalTime.parse(<span class="string">&quot;13:37&quot;</span>, germanFormatter);</span><br><span class="line">System.out.println(leetTime);   <span class="comment">// 13:37</span></span><br></pre></td></tr></table></figure><h2 id="本地日期类-LocalDate"><a href="#本地日期类-LocalDate" class="headerlink" title="本地日期类(LocalDate)"></a>本地日期类(LocalDate)</h2><p><code>LocalDate</code>表示一个确定的日期，比如<code>2014-03-11</code>。它与<code>LocalTime</code>类似，也是不可变的。以下例子展示如何通过年月日的加减生成新的日期。<br><strong>注意，每次操作都会返回一个新的实例。</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">LocalDate</span> <span class="variable">today</span> <span class="operator">=</span> LocalDate.now();</span><br><span class="line"><span class="type">LocalDate</span> <span class="variable">tomorrow</span> <span class="operator">=</span> today.plus(<span class="number">1</span>, ChronoUnit.DAYS);</span><br><span class="line"><span class="type">LocalDate</span> <span class="variable">yesterday</span> <span class="operator">=</span> tomorrow.minusDays(<span class="number">2</span>);</span><br><span class="line"></span><br><span class="line"><span class="type">LocalDate</span> <span class="variable">independenceDay</span> <span class="operator">=</span> LocalDate.of(<span class="number">2014</span>, Month.JULY, <span class="number">4</span>);</span><br><span class="line"><span class="type">DayOfWeek</span> <span class="variable">dayOfWeek</span> <span class="operator">=</span> independenceDay.getDayOfWeek();</span><br><span class="line">System.out.println(dayOfWeek);    <span class="comment">// FRIDAY</span></span><br></pre></td></tr></table></figure><p>从<code>String</code>(需要是符合日期格式的)转换为<code>LocalDate</code>与<code>LocalTime</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></pre></td><td class="code"><pre><span class="line"><span class="type">DateTimeFormatter</span> <span class="variable">germanFormatter</span> <span class="operator">=</span></span><br><span class="line">    DateTimeFormatter</span><br><span class="line">        .ofLocalizedDate(FormatStyle.MEDIUM)</span><br><span class="line">        .withLocale(Locale.GERMAN);</span><br><span class="line"></span><br><span class="line"><span class="type">LocalDate</span> <span class="variable">xmas</span> <span class="operator">=</span> LocalDate.parse(<span class="string">&quot;24.12.2014&quot;</span>, germanFormatter);</span><br><span class="line">System.out.println(xmas);   <span class="comment">// 2014-12-24</span></span><br></pre></td></tr></table></figure><h2 id="本地日期时间类-LocalDateTime"><a href="#本地日期时间类-LocalDateTime" class="headerlink" title="本地日期时间类(LocalDateTime)"></a>本地日期时间类(LocalDateTime)</h2><p><code>LocalDateTime</code>是日期与时间的组合。他讲上面提到的<code>LocalDate</code>与<code>LocalTime</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></pre></td><td class="code"><pre><span class="line"><span class="type">LocalDateTime</span> <span class="variable">sylvester</span> <span class="operator">=</span> LocalDateTime.of(<span class="number">2014</span>, Month.DECEMBER, <span class="number">31</span>, <span class="number">23</span>, <span class="number">59</span>, <span class="number">59</span>);</span><br><span class="line"></span><br><span class="line"><span class="type">DayOfWeek</span> <span class="variable">dayOfWeek</span> <span class="operator">=</span> sylvester.getDayOfWeek();</span><br><span class="line">System.out.println(dayOfWeek);      <span class="comment">// WEDNESDAY</span></span><br><span class="line"></span><br><span class="line"><span class="type">Month</span> <span class="variable">month</span> <span class="operator">=</span> sylvester.getMonth();</span><br><span class="line">System.out.println(month);          <span class="comment">// DECEMBER</span></span><br><span class="line"></span><br><span class="line"><span class="type">long</span> <span class="variable">minuteOfDay</span> <span class="operator">=</span> sylvester.getLong(ChronoField.MINUTE_OF_DAY);</span><br><span class="line">System.out.println(minuteOfDay);    <span class="comment">// 1439</span></span><br></pre></td></tr></table></figure><p>通过添加时区的操作可以使其很容易的转换为一个新的<code>Instant </code>实例。而且<code>Instant</code>很容易转换成先前版本的<code>java.util.Date</code>。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Instant</span> <span class="variable">instant</span> <span class="operator">=</span> sylvester</span><br><span class="line">        .atZone(ZoneId.systemDefault())</span><br><span class="line">        .toInstant();</span><br><span class="line"></span><br><span class="line"><span class="type">Date</span> <span class="variable">legacyDate</span> <span class="operator">=</span> Date.from(instant);</span><br><span class="line">System.out.println(legacyDate);     <span class="comment">// Wed Dec 31 23:59:59 CET 2014</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="type">DateTimeFormatter</span> <span class="variable">formatter</span> <span class="operator">=</span></span><br><span class="line">    DateTimeFormatter</span><br><span class="line">        .ofPattern(<span class="string">&quot;MMM dd, yyyy - HH:mm&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="type">LocalDateTime</span> <span class="variable">parsed</span> <span class="operator">=</span> LocalDateTime.parse(<span class="string">&quot;Nov 03, 2014 - 07:13&quot;</span>, formatter);</span><br><span class="line"><span class="type">String</span> <span class="variable">string</span> <span class="operator">=</span> formatter.format(parsed);</span><br><span class="line">System.out.println(string);     <span class="comment">// Nov 03, 2014 - 07:13</span></span><br></pre></td></tr></table></figure><blockquote><p>与<code>java.text.NumberFormat</code>不同,<code>DateTimeFormatter</code>是<code>不可变的</code>且<code>线程安全的</code>。</p></blockquote><h1 id="注解-Annotations"><a href="#注解-Annotations" class="headerlink" title="注解(Annotations)"></a>注解(Annotations)</h1><p>Annotations在Java8中是可以重复的(支持重载的)，比如下面的例子:</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="meta">@interface</span> Hints &#123;</span><br><span class="line">    Hint[] value();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Repeatable(Hints.class)</span></span><br><span class="line"><span class="meta">@interface</span> Hint &#123;</span><br><span class="line">    String <span class="title function_">value</span><span class="params">()</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>Java8能够通过声明注解@Repeable来使用相同类型的多个注解。</p></blockquote><p><strong>使用注解容器(老派)</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Hints(&#123;@Hint(&quot;hint1&quot;), @Hint(&quot;hint2&quot;)&#125;)</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Person</span> &#123;&#125;</span><br></pre></td></tr></table></figure><p><strong>使用Repeatable注解(新派)</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Hint(&quot;hint1&quot;)</span></span><br><span class="line"><span class="meta">@Hint(&quot;hint2&quot;)</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Person</span> &#123;&#125;</span><br></pre></td></tr></table></figure><blockquote><p>在使用第二种方式的时候，Java编译器会隐式的声明<code>@Hints</code>注解。</p></blockquote><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></pre></td><td class="code"><pre><span class="line"><span class="type">Hint</span> <span class="variable">hint</span> <span class="operator">=</span> Person.class.getAnnotation(Hint.class);</span><br><span class="line">System.out.println(hint);                   <span class="comment">// null</span></span><br><span class="line"></span><br><span class="line"><span class="type">Hints</span> <span class="variable">hints1</span> <span class="operator">=</span> Person.class.getAnnotation(Hints.class);</span><br><span class="line">System.out.println(hints1.value().length);  <span class="comment">// 2</span></span><br><span class="line"></span><br><span class="line">Hint[] hints2 = Person.class.getAnnotationsByType(Hint.class);</span><br><span class="line">System.out.println(hints2.length);          <span class="comment">// 2</span></span><br></pre></td></tr></table></figure><blockquote><p>虽然我们从来没有在<code>Person</code>类上声明过<code>@Hints</code>注解，但它仍能通过<code>getAnnotation(Hints.class)</code>读取到。而更方便的方式是通过<code>getAnnotationsByType</code>,它能赋予所有<code>@Hint</code>注解的直接访问权限。</p></blockquote><p>Java8中注解中拓展了两个新的使用目标(Target)：</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">@Target(&#123;ElementType.TYPE_PARAMETER, ElementType.TYPE_USE&#125;)</span></span><br><span class="line"><span class="meta">@interface</span> MyAnnotation &#123;&#125;</span><br></pre></td></tr></table></figure><p>文章翻译自以下地址：</p><ul><li><a href="https://winterbe.com/posts/2014/03/16/java-8-tutorial/">原始博客地址</a></li><li><a href="https://github.com/winterbe/java8-tutorial">github地址</a></li></ul>]]></content>
    
    
    <summary type="html">Java8新特性简单介绍</summary>
    
    
    
    <category term="分类" scheme="https://newgr8player.top/categories/%E5%88%86%E7%B1%BB/"/>
    
    
    <category term="标签" scheme="https://newgr8player.top/tags/%E6%A0%87%E7%AD%BE/"/>
    
  </entry>
  
  <entry>
    <title>深入理解Java虚拟机 - 第八章</title>
    <link href="https://newgr8player.top/posts/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Java%E8%99%9A%E6%8B%9F%E6%9C%BA---%E7%AC%AC%E5%85%AB%E7%AB%A0/"/>
    <id>https://newgr8player.top/posts/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Java%E8%99%9A%E6%8B%9F%E6%9C%BA---%E7%AC%AC%E5%85%AB%E7%AB%A0/</id>
    <published>2018-07-18T10:02:43.000Z</published>
    <updated>2024-12-13T07:02:02.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="第八章-虚拟机字节码执行引擎"><a href="#第八章-虚拟机字节码执行引擎" class="headerlink" title="第八章 虚拟机字节码执行引擎"></a>第八章 虚拟机字节码执行引擎</h2><h3 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h3><p><strong>执行引擎</strong>是Java虚拟机最核心的组成之一。</p><h3 id="运行时栈帧结构"><a href="#运行时栈帧结构" class="headerlink" title="运行时栈帧结构"></a>运行时栈帧结构</h3><p><strong>栈帧（Stack Frame）</strong>是用于支持方法调用和方法执行的数据结构。它是虚拟机运行时数据区中的虚拟机栈的栈元素，存储了方法的局部变量表、操作数栈、动态链接和方法返回地址等信息。每一个方法从调用开始到执行完成的过程，就对应着一个栈帧在虚拟机栈里面从入栈道出栈的过程。<br>栈帧的概念结构图：<br><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/25/3c6a3190-c695-11e9-a6ef-abdd4d48672e.jpg" alt="栈帧的概念结构.jpg"></p><h4 id="局部变量表"><a href="#局部变量表" class="headerlink" title="局部变量表"></a>局部变量表</h4><p>局部变量表是一组变量值存储空间，用于存放方法的参数和方法类定义的局部变量。<br>它是以变量槽（Variable Slot）为最小单位，每一个Slot（32位）都应该能放一个 boolean 、byte 、char 、float 、 reference 、 short 、 int 或 returnAddress 类型数据。而至于 long 、double 两种数据则被规定为是64位数据（连续两个32位）。<br><em>reference类型数据可能食32位，也可能是64位数据</em></p><h4 id="操作数栈"><a href="#操作数栈" class="headerlink" title="操作数栈"></a>操作数栈</h4><blockquote><p>也被称之为操作栈，是一个先进先出（FIFO）的栈。 </p></blockquote><p>操作数栈的最大深度也在编译的时候就被写入到code属性的 <code>max_stacks</code> 数据项之中。<br>在概念模型中，两个栈帧相互之间是完全独立的，但是大多数虚拟机会做一些优化处理，令两个栈帧出现一部分重叠，这样在方法调用时就可以公用一部分数据，无须进行额外的参数复制传递的操作。如下图示：<br><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/25/25822130-c696-11e9-a6ef-abdd4d48672e.png" alt="两个栈帧之间的共享数据.png"></p><h4 id="动态连接"><a href="#动态连接" class="headerlink" title="动态连接"></a>动态连接</h4><p>每个栈帧都包含一个运行时常量池中指向该栈帧所属方法的引用，这是为了支持方法调用过程中的<strong>动态连接</strong>。</p><h4 id="方法返回地址"><a href="#方法返回地址" class="headerlink" title="方法返回地址"></a>方法返回地址</h4><p>当一个方法被执行时，有两种方式推出这个方法：</p><ul><li>正常完成出口（Normal Method Invocation Completion）</li><li>异常完成出口（Abrupt Method Invocation Completion）</li></ul><h4 id="附加信息"><a href="#附加信息" class="headerlink" title="附加信息"></a>附加信息</h4><p>虚拟机规范允许具体的虚拟机实现增加一些规范里没有描述的信息到栈帧之中。</p><h3 id="方法调用"><a href="#方法调用" class="headerlink" title="方法调用"></a>方法调用</h3><p>确定被调用方法的版本（即调用哪一个方法）。</p><h4 id="方法解析"><a href="#方法解析" class="headerlink" title="方法解析"></a>方法解析</h4><p>调用目标在程序写好、编译器进行编译时就确定下来，这种方法的调用称之为<strong>解析（Resolution）</strong>。<br>简单总结就是：<strong>编译器可知，运行期不可变</strong>。<br>在Java中符合这种要求的方法主要有：静态方法和私有方法</p><h4 id="分派"><a href="#分派" class="headerlink" title="分派"></a>分派</h4><h5 id="静态分派"><a href="#静态分派" class="headerlink" title="静态分派"></a>静态分派</h5><p><code>Human man = new Man();</code><br><strong>静态类型</strong>：<code>Human</code><br><strong>实际类型</strong>：<code>Man</code><br>在编译阶段，Javac编译器就根据参数的静态类型决定使用哪个重载版本。</p><h5 id="动态分派"><a href="#动态分派" class="headerlink" title="动态分派"></a>动态分派</h5><p>在运行期间根据实际类型确定方法执行版本的分配过程称为动态分派。</p><h5 id="单分派与多分派"><a href="#单分派与多分派" class="headerlink" title="单分派与多分派"></a>单分派与多分派</h5><p><strong>单分派</strong>是根据一个宗量对目标方法进行选择。<br><strong>多分派</strong>则是根据多于一个的宗量对目标方法进行选择。</p><p><strong>宗量</strong>：方法的接收者与方法的参数统称为方法的宗量。<br><strong>单分派定义</strong><br>根据一个宗量对目标方法进行选择，即为单分派。(动态分派属于单分派)<br><strong>多分派</strong><br>根据多于一个宗量对目标方法进行选择，即为多分派。(静态分派属于多分派)</p>]]></content>
    
    
    <summary type="html">深入理解Java虚拟机 - 第八章 虚拟机字节码执行引擎</summary>
    
    
    
    <category term="Java" scheme="https://newgr8player.top/categories/Java/"/>
    
    
    <category term="Java" scheme="https://newgr8player.top/tags/Java/"/>
    
    <category term="JVM" scheme="https://newgr8player.top/tags/JVM/"/>
    
    <category term="笔记" scheme="https://newgr8player.top/tags/%E7%AC%94%E8%AE%B0/"/>
    
  </entry>
  
  <entry>
    <title>深入理解Java虚拟机 - 第七章</title>
    <link href="https://newgr8player.top/posts/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Java%E8%99%9A%E6%8B%9F%E6%9C%BA---%E7%AC%AC%E4%B8%83%E7%AB%A0/"/>
    <id>https://newgr8player.top/posts/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Java%E8%99%9A%E6%8B%9F%E6%9C%BA---%E7%AC%AC%E4%B8%83%E7%AB%A0/</id>
    <published>2018-07-18T05:22:36.000Z</published>
    <updated>2024-12-13T07:02:02.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="第七章-虚拟机类的加载机制"><a href="#第七章-虚拟机类的加载机制" class="headerlink" title="第七章 虚拟机类的加载机制"></a>第七章 虚拟机类的加载机制</h2><h3 id="7-1-概述"><a href="#7-1-概述" class="headerlink" title="7.1 概述"></a>7.1 概述</h3><p>虚拟机把描述类的数据从Class文件加载到内存，并对数据进行校验、转换解析和初始化，最终形成可以被虚拟机直接使用的Java类型，这就是<strong>虚拟机的类的加载机制</strong>。</p><h3 id="7-2-类的加载时机"><a href="#7-2-类的加载时机" class="headerlink" title="7.2 类的加载时机"></a>7.2 类的加载时机</h3><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/25/43552f10-c6e4-11e9-ad5a-c9a6da72bdb3.jpg" alt="类的生命周期.jpg"></p><ul><li><strong>加载（Loading）</strong>：按照虚拟机规范，<strong>有且只有</strong>以下四种情况下必须立即对类进行“初始化”：</li><li>遇到new、getstatic、putstatic或invokestatic这4条字节码指令时，如果类没有初始化，则需要先出发其初始化。生成这4条指令的典型场景是：使用 <code>new</code> 关键字实例化对象的时候、读取或设置一个类的静态字段的时候（被final修饰、已在编译期把结果放入常量池的静态字段除外）、以及调用一个类的静态方法的时候</li><li>使用 <code>java.lang.reflect</code> 包的方法对类进行反射调用的时候</li><li>当初始化一个类时，如果发现它的父类还没有进行初始化，则需要先触发其父类进行初始化</li><li>当虚拟机启动时，用户需要指定一个要执行的主类（包含main方法的类），虚拟机会先初始化这个类</li></ul><h3 id="7-3-类的加载过程"><a href="#7-3-类的加载过程" class="headerlink" title="7.3 类的加载过程"></a>7.3 类的加载过程</h3><p>加载、验证、准备、解析、初始化</p><h4 id="7-3-1-加载"><a href="#7-3-1-加载" class="headerlink" title="7.3.1 加载"></a>7.3.1 加载</h4><p><strong>加载（Loading）</strong>阶段，虚拟机需要完成以下三件事：</p><ul><li>通过一个类的全限定名来获取定义这个类对应的二进制字节流</li><li>将这个类的二进制字节流所代表的静态存储结构转换为方法区的运行时数据结构</li><li>在Java堆中生成一个代表这个类的 <code>java.lang.Class</code> 对象，作为方法区这些数据的访问入口。</li></ul><h3 id="7-3-2-验证"><a href="#7-3-2-验证" class="headerlink" title="7.3.2 验证"></a>7.3.2 验证</h3><p>验证是虚拟机对自身保护的一项重要工作。</p><p>大致完成以下四个阶段的检验过程：</p><ul><li><strong>文件格式验证</strong>，验证字节流是否符合Class文件格式规范，并且能被当前版本的虚拟机处理。</li><li><strong>元数据验证</strong>，对字节码描述的信息进行语义分析，以保证其描述的信息符合Java语言规范要求。</li><li><strong>字节码验证</strong>，主要工作是进行数据流和控制流分析，保证被校验类的方法不会危害到虚拟机的安全。</li><li><strong>符号引用验证</strong>，可以看作是对类自身以外（常量池中的各种符号引用）的信息进行匹配性的校验。</li></ul><h3 id="7-3-3-准备"><a href="#7-3-3-准备" class="headerlink" title="7.3.3 准备"></a>7.3.3 准备</h3><p>准备阶段是正式为类变量分配内存并设置类变量初始值的阶段，这些内存都将在方法区中进行分配。</p><h3 id="7-3-4-解析"><a href="#7-3-4-解析" class="headerlink" title="7.3.4 解析"></a>7.3.4 解析</h3><p>是虚拟机将常量池中的符号引用替换为直接引用的过程。</p><p>解析动作主要针对类或接口、字段、类方法、接口方法四类符号引用分别进行。</p><h3 id="7-3-5-初始化"><a href="#7-3-5-初始化" class="headerlink" title="7.3.5 初始化"></a>7.3.5 初始化</h3><p>真正开始执行类中定义的Java程序代码（或者说是字节码）。</p><h2 id="7-4-类加载器"><a href="#7-4-类加载器" class="headerlink" title="7.4 类加载器"></a>7.4 类加载器</h2><p>类加载过程中的 <em>通过一个类的全限定名来获取描述这个类的二进制字节流”</em> 这个动作是放在Java虚拟机的外部来实现的，以便于让应用程序自己来决定如何去获取所需要的类，实现这个动作的代码模块被称为 <strong>“类加载器”</strong>。</p><h3 id="7-4-1-类和类加载器"><a href="#7-4-1-类和类加载器" class="headerlink" title="7.4.1 类和类加载器"></a>7.4.1 类和类加载器</h3><p><strong>类加载器</strong> 虽然只用于实现类的加载动作，但是它的作用却远远不限于此，比较两个类是否“相等”，不仅仅要确认这两个类是否来源于同一个class文件，还需要加载这两个类的类加载器相同。</p><h3 id="7-4-2-双亲委派模型"><a href="#7-4-2-双亲委派模型" class="headerlink" title="7.4.2 双亲委派模型"></a>7.4.2 双亲委派模型</h3><p>站在虚拟机的角度，只存在两种类加载器：</p><ul><li><strong>启动类加载器（Bootstrap ClassLoader）</strong>，使用C++实现，是虚拟机的一部分</li><li>其他类加载器，由Java语言实现，独立于虚拟机之外的，全部继承自抽象类 <code>java.lang.ClassLoader</code></li></ul><p>从开发人员的角度，类加载器可以划分得更细致一些：</p><ul><li><strong>启动类加载器（Bootstrap ClassLoader）</strong>：负责将存放在 <code>&lt;JAVA_HOME&gt;\lib</code> 目录中的，或者被 <code>-Xbootclasspath</code> 参数所指定的路径中的，并且是虚拟机识别的类库加载到虚拟机内存中。</li><li><strong>扩展类加载器（Extension ClassLoader）</strong>：负责加载 <code>&lt;JAVA_HOME&gt;\lib\ext</code> 目录下的，或者被 <code>java.ext.dirs</code> 系统变量所指定的路径中的所有类库，开发者可以直接使用扩展类加载器。</li><li><strong>应用程序类加载器（Application ClassLoader）</strong>：负责加载用户类路径（ClassPath）上所指定的类库，一般情况下这个就是程序中默认的类加载器。</li></ul><p>以上加载器互相配合来加载我们自己的应用程序，如果有必要，我们还可以加入自己定义的加载器。这些加载器之间的关系一般如下图示：<br><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/25/531ccbb0-c6e4-11e9-ad5a-c9a6da72bdb3.png" alt="双亲委派模型.png"><br>类加载器的<strong>双亲委派模型（Parent Delegation Model）</strong>：要求除了顶层的启动类加载器外，其余的类加载器都必须有自己的父类加载器。（ 这里类加载器之间的父子关系一般不会以继承（Inheritance）来实现，而是使用组合（Composition）来复用父加载器的代码）。这种模型被广泛使用于几乎所有的Java程序中，但是它并不是一个强制性的约束，只是Java设计者推荐给开发者使用的一种类加载器实现方式。</p><p><em>双亲委派模型的具体工作过程是：如果一个类收到了加载请求，它首先不会尝试自己去加载这个类，而是把这个请求委派给他的父类加载器去完成，每一层次的加载类都是如此，因此所有的加载请求都会传递给最顶层的启动类加载器中，只有当父加载器反馈自己无法完成这个加载请求（它的搜索范围内找不到需要加载的类）时，子类才会尝试自己去加载。</em></p><p>好处：java类随着它的类加载器一起具备了一种带有优先层级的层次关系，保证了Java程序的稳定运行。</p><h3 id="7-4-3-破坏双亲委派模型"><a href="#7-4-3-破坏双亲委派模型" class="headerlink" title="7.4.3 破坏双亲委派模型"></a>7.4.3 破坏双亲委派模型</h3><p>OSGi</p>]]></content>
    
    
    <summary type="html">深入理解Java虚拟机 - 第七章 虚拟机类的加载机制 虚拟机类的加载机制</summary>
    
    
    
    <category term="Java" scheme="https://newgr8player.top/categories/Java/"/>
    
    
    <category term="Java" scheme="https://newgr8player.top/tags/Java/"/>
    
    <category term="JVM" scheme="https://newgr8player.top/tags/JVM/"/>
    
    <category term="笔记" scheme="https://newgr8player.top/tags/%E7%AC%94%E8%AE%B0/"/>
    
  </entry>
  
  <entry>
    <title>深入理解Java虚拟机 - 第六章</title>
    <link href="https://newgr8player.top/posts/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Java%E8%99%9A%E6%8B%9F%E6%9C%BA---%E7%AC%AC%E5%85%AD%E7%AB%A0/"/>
    <id>https://newgr8player.top/posts/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Java%E8%99%9A%E6%8B%9F%E6%9C%BA---%E7%AC%AC%E5%85%AD%E7%AB%A0/</id>
    <published>2018-07-18T00:59:28.000Z</published>
    <updated>2024-12-13T07:02:02.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="第六章-类文件结构"><a href="#第六章-类文件结构" class="headerlink" title="第六章 类文件结构"></a>第六章 类文件结构</h2><h3 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h3><p>现在越来越多的程序语言选择了与操作系统无关和机器指令无关的、平台中立的格式作为程序编译后的存储格式。</p><h3 id="无关性的基石"><a href="#无关性的基石" class="headerlink" title="无关性的基石"></a>无关性的基石</h3><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/25/aa7ccea0-c6e4-11e9-ad5a-c9a6da72bdb3.png" alt="Java虚拟机提供的语言无关性.png"></p><h3 id="Class类文件的结构"><a href="#Class类文件的结构" class="headerlink" title="Class类文件的结构"></a>Class类文件的结构</h3><p>Class文件是一组以8位字节为基础单位的二进制流，各个数据项目严格按照顺序紧凑地排列在Class文件中，中间没有添加任何分隔符。当遇到需要占用8位字节以上空间的数据项时，则会按照高位在前的方式分割成若干个8位字节进行存储。</p><p>按照Java虚拟机规范，Class文件格式采用一种类似于C语言结构体的伪结构来存储，这种结构中只有两种数据类型：无符号数和表。</p><ul><li><strong>无符号数</strong>属于基本的数据类型，以u1、u2、u4、u8来分别代表1个字节、2个字节、4个字节、和8个字节的无符号数，无符号数可以用来描述数字、索引引用、数量值、或者按照UTF-8编码构成字符串值。</li><li><strong>表</strong>是由多个无符号数或其他表作为数据项构成的复合数据类型，所有的表都习惯性地以<code>&quot;_info&quot;</code>结尾。表用于描述有层次关系的复合数据结构的数据，整个Class文件本质上就是一张表，它由如下所示的数据项构成：<br><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/25/bbcd5530-c6e4-11e9-ad5a-c9a6da72bdb3.jpg" alt="Class文件格式.jpg"><br>无论是无符号数还是表，当需要描述同一类型单数量不定的多个数据时，经常会使用一个前置的容量计数器加若干个连续的数据项的形式，这时候称这一系列连续的某一类型的数据位某一类型的集合。</li></ul><h4 id="魔数与Class文件的版本"><a href="#魔数与Class文件的版本" class="headerlink" title="魔数与Class文件的版本"></a>魔数与Class文件的版本</h4><p>每个Class文件的头4个字节称为魔数（Magic Number），它的唯一作用时用语确定这个文件是否作为一个能被虚拟机接受的Class文件。Class文件的魔数值是<code>0xCAFEBABE</code>。</p><p>紧挨着魔数的4个字节存储的是Class文件的版本号：第5和6个字节是次版本号（Minor Version），第7和8个字节是主版本号（Major Version）。Java的版本号是从45开始的，以后每个大版本发布后，主版本号向上加1，高版本的JDK能向下兼容以前版本的Class文件，但不能运行以后版本的Class文件，即使文件格式并未发生变化。</p><h4 id="常量池"><a href="#常量池" class="headerlink" title="常量池"></a>常量池</h4><p>紧接着主次版本号之后的是常量池入口，它是Class文件中与其它项目关联最紧密的数据类型，也是占用Class文件空间最大的数据项目之一，同时还是Class文件中第一个出现的表类型数据项目。</p><p>常量池中主要存放两大类常量：字面量（Literal）和符号引用（Symbolic Reference）。字面量比较接近Java语言的常量概念，如文本字符串、被声明为final的常量值等。而符号引用则属于编译原理方面的概念，包括了下面三类常量：</p><ul><li>类和接口的全限定名（Fully Quaified Name）</li><li>字段的名称和描述</li><li>方法的名称和描述符</li></ul><p>常量池中每一项常量都是一个表<br><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/25/c5578080-c6e4-11e9-ad5a-c9a6da72bdb3.png" alt="1240 1.png"></p><p>每一项的表结构<br><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/25/ca9a3e20-c6e4-11e9-ad5a-c9a6da72bdb3.png" alt="1240 2.png"></p><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/25/ca6a2d70-c6e4-11e9-ad5a-c9a6da72bdb3.png" alt="1240 3.png"></p><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/25/ca9ef910-c6e4-11e9-ad5a-c9a6da72bdb3.png" alt="1240 4.png"></p><h4 id="访问标志"><a href="#访问标志" class="headerlink" title="访问标志"></a>访问标志</h4><p><strong>访问标志（access_flag）</strong>：用于识别一些类或接口层次的访问信息，包括：这个Class是类还是接口，是否定义为public类型，是否定义为abstract类型，如果是类的话，是否被声明为final，等等。</p><p>具体的标志位及含义，见下表：<br><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/25/e45b1b90-c6e4-11e9-ad5a-c9a6da72bdb3.jpg" alt="Class文件的访问标志.jpg"></p><h4 id="类索引、父类索引、接口索引集合"><a href="#类索引、父类索引、接口索引集合" class="headerlink" title="类索引、父类索引、接口索引集合"></a>类索引、父类索引、接口索引集合</h4><p>类索引（this_class）和父类索引（super_class）都是一个u2类型的数据，而接口索引集合是一个u2类型的数据集合，class文件由这三个数据来确定这个类的继承关系。</p><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/25/ede790d0-c6e4-11e9-ad5a-c9a6da72bdb3.png" alt="1240 5.png"></p><h4 id="字段表集合"><a href="#字段表集合" class="headerlink" title="字段表集合"></a>字段表集合</h4><p><strong>字段表（field_info）</strong>：用于描述类或者接口的声明的变量，不包括方法的内部声明变量。</p><ul><li>字段表（field_info）用于描述接口或者类中声明的变量</li><li>字段（field）包括类级变量以及实例级变量，但不包括在方法内部声明的局部变量</li></ul><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/25/f3480fa0-c6e4-11e9-ad5a-c9a6da72bdb3.png" alt="1240 6.png"></p><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/25/f357a000-c6e4-11e9-ad5a-c9a6da72bdb3.png" alt="1240 7.png"></p><p>name_index是对常量池的引用，代表着字段的简单名称<br>descriptor_index是对常量池的引用，代表字段的描述符</p><p>Java中一个字段（field）可以包含的信息包括：字段的作用域（public、private、protected）、类级别还是实例级别（static）、可变性（final）、并发可见性（volatile是否强制从主内存读写）、可否序列化（transient）、字段数据类型（基本类型、对象、数组）、名称。</p><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/25/feb3cc30-c6e4-11e9-ad5a-c9a6da72bdb3.png" alt="1240 8.png"></p><h4 id="方法表集合"><a href="#方法表集合" class="headerlink" title="方法表集合"></a>方法表集合</h4><p>方法表的结构如同 【Chapter 6.3.5 字段表集合】一样，一次包括了，访问标识（access_flag）、名称索引（name_index）、描述符索引（descriptor_index）、属性表集合（attributes）。</p><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/25/0584caf0-c6e5-11e9-ad5a-c9a6da72bdb3.png" alt="1240 9.png"></p><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/25/059bfc70-c6e5-11e9-ad5a-c9a6da72bdb3.png" alt="1240 10.png"></p><h4 id="属性表集合"><a href="#属性表集合" class="headerlink" title="属性表集合"></a>属性表集合</h4><p><strong>属性表集合（attribute_info）</strong>：用于描述某些场景（Class文件、字段表、方法表）的专有信息。</p><ul><li>属性表（attribute_info）在前面的讲解之中已经出现过数次，在Class文件、字段表、方法表都可以携带自己的属性表集合，以用于描述某些场景专有的信息。</li></ul><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/25/12e356d0-c6e5-11e9-ad5a-c9a6da72bdb3.png" alt="1240 11.png"></p><ul><li>虚拟机规范定义的属性</li></ul><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/25/1b747c20-c6e5-11e9-ad5a-c9a6da72bdb3.png" alt="1240 12.png"></p><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/25/1b7d07a0-c6e5-11e9-ad5a-c9a6da72bdb3.png" alt="1240 13.png"></p><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/25/1b773b40-c6e5-11e9-ad5a-c9a6da72bdb3.png" alt="1240 14.png"></p><ul><li>Code属性Java程序方法体中的代码经过Javac编译器处理后，最终变为字节码指令存储在Code属性内。Code属性出现在方法表的属性集合之中，但并非所有的方法表都必须存在这个属性，譬如接口或者抽象类中的方法就不存在Code属性</li></ul><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/25/2995bbc0-c6e5-11e9-ad5a-c9a6da72bdb3.png" alt="1240 15.png"></p><p><strong>Code属性</strong><br>Java程序方法体里面的代码经过javac编译器处理之后，最终会变成字节码指令存储在Code属性内。<br><strong>Exceptions属性</strong><br>列举出方法体中可能抛出的受查异常（checked exception），也就是方法描述时在throws关键字后面的异常。<br><strong>LineNumberTable属性</strong><br>用于描述Java字节码行号与源代码行号之间的对应关系（字节码的偏移量）。<br><strong>LocalVariableTable属性</strong><br>用于描述栈帧中局部变量表中的变量和Java源代码中定义的变量之间的关系。<br><strong>SourceFile属性</strong><br>用于记录生成这个Class文件的源码文件的名称。<br><strong>ConstantValue属性</strong><br>通知虚拟机自动为静态变量赋值。<br><strong>InnerClasses属性</strong><br>用于记录内部类与宿主之间的关联。<br><strong>Deprecated和Synthetic属性</strong><br>Deprecated属性用于表示某个类、字段或者方法，已经被程序作者定位废弃不推荐使用，可以在代码中通过 <code>@deprecated</code> 注释来设置。<br>Synthetic属性代表字段或方法并不是由Java源代码直接产生的，而是由编译器自行添加的。</p>]]></content>
    
    
    <summary type="html">深入理解Java虚拟机 - 第六章 类文件结构</summary>
    
    
    
    <category term="Java" scheme="https://newgr8player.top/categories/Java/"/>
    
    
    <category term="Java" scheme="https://newgr8player.top/tags/Java/"/>
    
    <category term="JVM" scheme="https://newgr8player.top/tags/JVM/"/>
    
    <category term="笔记" scheme="https://newgr8player.top/tags/%E7%AC%94%E8%AE%B0/"/>
    
  </entry>
  
  <entry>
    <title>深入理解Java虚拟机 - 第五章</title>
    <link href="https://newgr8player.top/posts/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Java%E8%99%9A%E6%8B%9F%E6%9C%BA---%E7%AC%AC%E4%BA%94%E7%AB%A0/"/>
    <id>https://newgr8player.top/posts/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Java%E8%99%9A%E6%8B%9F%E6%9C%BA---%E7%AC%AC%E4%BA%94%E7%AB%A0/</id>
    <published>2018-07-18T00:45:26.000Z</published>
    <updated>2024-12-13T07:02:02.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="第五章-调优案例分析与实战"><a href="#第五章-调优案例分析与实战" class="headerlink" title="第五章 调优案例分析与实战"></a>第五章 调优案例分析与实战</h1><h2 id="案例分析"><a href="#案例分析" class="headerlink" title="案例分析"></a>案例分析</h2><h3 id="高性能硬件上的程序部署策略"><a href="#高性能硬件上的程序部署策略" class="headerlink" title="高性能硬件上的程序部署策略"></a>高性能硬件上的程序部署策略</h3><p><strong>问题描述</strong></p><ul><li>一个每天15万PV左右的在线文档网站升级了硬件，4个CPU，16GB物理内存，操作系统为64位CentOS 5.4，使用Resin作为Web服务器，没有部署其他的应用。</li><li>管理员选用了64位的JDK 1.5，并通过-Xmx和-Xms参数将Java堆固定在12GB。</li><li>使用一段时间不定期出现长时间失去响应的情况；<br><strong>问题分析</strong></li><li>升级前使用32位系统，Java堆设置为1.5GB，只是感觉运行缓慢没有明显的卡顿；</li><li>通过监控发现是由于GC停顿导致的，虚拟机运行在Server模式，默认使用吞吐量优先收集器，回收12GB的堆，一次Full GC的停顿时间高达14秒；</li><li>并且由于程序设计的原因，很多文档从磁盘加载到内存中，导致内存中出现很多由文档序列化生成的大对象，这些大对象进入了老年代，没有在Minor GC中清理掉；<br><strong>解决办法</strong></li><li>在虚拟机上建立5个32位的JDK逻辑集群，每个进程按2GB内存计算（其中堆固定为1.5GB），另外建议一个Apache服务作为前端均衡代理访问门户；</li><li>另外考虑服务压力主要在磁盘和内存访问，CPU资源敏感度较低，因此改为CMS收集器；</li><li>最终服务没有再出现长时间停顿，速度比硬件升级前有较大提升；</li></ul><h3 id="集群间同步导致的内存溢出"><a href="#集群间同步导致的内存溢出" class="headerlink" title="集群间同步导致的内存溢出"></a>集群间同步导致的内存溢出</h3><p><strong>问题描述</strong></p><ul><li>一个基于B&#x2F;S的MIS系统，硬件为两台2个CPU、8GB内存的HP小型机，服务器为WebLogic 9.2，每台机器启动了3个WebLogic实例，构建一个6台节点的亲和式集群（一个固定的用户请求永远分配到固定的节点处理）。</li><li>由于有部分数据需要共享，原先采用数据库，后因为读写性能问题使用了JBossCache构建了一个全局缓存；</li><li>正常使用一段较长的时间，最近不定期出现了多次的内存溢出问题；<br><strong>问题分析</strong></li><li>监控发现，服务内存回收状况一直正常，每次内存回收后都能恢复到一个稳定的可用空间</li><li>此次未升级业务代码，排除新修改代码引入的内存泄漏问题；</li><li>服务增加-XX:+HeapDumpOnOutOfMemoryError参数，在最近一次内存溢出时，分析heapdump文件发现存在大量的org.jgroups.protocols.pbcast,NAKACK对象；</li><li>最终分析发现是由于JBossCache的NAKACK栈在页面产生大量请求时，有个负责安全校验的全局Filter导致集群各个节点之间网络交互非常频繁，当网络情况不能满足传输要求时，大量的需要失败重发的数据在内存中不断堆积导致内存溢出。<br><strong>解决办法</strong></li></ul><ul><li>JBossCache版本改进；</li><li>程序设计优化，JBossCahce集群缓存同步，不大适合有频繁写操作的情况；</li></ul><h3 id="堆外内存导致的溢出错误"><a href="#堆外内存导致的溢出错误" class="headerlink" title="堆外内存导致的溢出错误"></a>堆外内存导致的溢出错误</h3><p><strong>问题描述</strong></p><ul><li>一个学校的小型项目，基于B&#x2F;S的电子考试系统，服务器是Jetty 7.1.4，硬件是一台普通PC机，Core i5 CPU，4GB内存，运行32位Windows操作系统；</li><li>为了实现客户端能实时地从服务器端接收考试数据，使用了逆向AJAX技术（也称为Comet或Server Side Push），选用CometD 1.1.1作为服务端推送框架；</li><li>测试期间发现服务端不定期抛出内存溢出；加入-XX:+HeapDumpOnOutOfMemoryError后抛出内存溢出时什么问题都没有，采用jstat观察GC并不频繁且GC回收正常；最后在内存溢出后从系统日志发现如下异常堆栈：<br><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/25/5d8fb3e0-c6e5-11e9-ad5a-c9a6da72bdb3.png" alt="example_direct_memory_error.png"><br><strong>问题分析</strong></li><li>在第二章里曾经说过直接内存溢出的场景，垃圾收集时，虚拟机虽然会对直接内存进行回收，但它只能等老年代满了触发Full GC时顺便清理，否则只能等内存溢出时catch住然后调用System.gc()，如果虚拟机还是不听（比如打开了-XX:+DisableExplictGC）则只能看着堆中还有许多空闲内存而溢出；</li><li>本案例中的CometD框架正好有大量的NIO操作需要使用直接内存；</li></ul><h3 id="外部命令导致系统缓慢"><a href="#外部命令导致系统缓慢" class="headerlink" title="外部命令导致系统缓慢"></a>外部命令导致系统缓慢</h3><p><strong>问题描述</strong></p><ul><li>一个数字校园应用系统，运行在一个4个CPU的Solaris 10操作系统上，中间件为GlassFish服务器；</li><li>系统在做大并发压力测试时，发现请求响应时间比较慢，通过监控工具发现CPU使用率很高，并且系统占用绝大多数的CPU资源的程序并不是应用系统本身；</li><li>通过Dtrace脚本发现最消耗CPU的竟然是fork系统调用（Linux用来产生新进程的）；<br><strong>问题分析</strong></li><li>最终发现是每个用户请求需要执行一个外部的shell脚本来获取一些系统信息，是通过Runtime.getRuntime().exec()方法调用的；</li><li>Java虚拟机在执行这个命令时先克隆一个和当前虚拟机拥有一样环境变量的进程，再用这个新进程去执行外部命令，如果频繁地执行这个操作，系统消耗会很大；</li><li>最终修改时改用Java的API去获取这些信息，系统恢复了正常；</li></ul><h3 id="服务器JVM进程奔溃"><a href="#服务器JVM进程奔溃" class="headerlink" title="服务器JVM进程奔溃"></a>服务器JVM进程奔溃</h3><p><strong>问题描述</strong></p><ul><li>一个基于B&#x2F;S的MIS系统，硬件为两台2个CPU、8GB内存的HP系统，服务器是WebLogic 9.2（和案例”集群间同步导致的内存溢出”相同的系统）；</li><li>正常运行一段时间后发现运行期间频繁出现集群节点的虚拟机进程自动关闭的现象，留下一个hs_err_pid###.log，奔溃前不久都发生大量相同的异常，日志如下所示：<br><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/25/6788a690-c6e5-11e9-ad5a-c9a6da72bdb3.png" alt="example_jvm_coredown.png"><br><strong>问题分析</strong></li><li>这是一个远端断开连接的异常，得知在MIS系统工作流的待办事项变化时需要通过Web服务通知OA门户系统；</li><li>通过SoapUI测试发现调用后竟然需要长达3分钟才能返回，并且返回结果都是连接中断；</li><li>由于MIS使用异步方式调用，两边处理速度不对等，导致在等待的线程和Socket连接越来越多，最终在超过虚拟机承受能力后进场奔溃；</li><li>解决方法：将异步调用修改为生产者&#x2F;消费者模型的消息队列处理，系统恢复正常；</li></ul><h3 id="不恰当数据结构导致内存占用过大"><a href="#不恰当数据结构导致内存占用过大" class="headerlink" title="不恰当数据结构导致内存占用过大"></a>不恰当数据结构导致内存占用过大</h3><p><strong>问题描述</strong></p><ul><li>有一个后台RPC服务器，使用64位虚拟机，内存配置为-Xms4g -Xmx8g -Xmn1g，使用ParNew + CMS的收集器组合；</li><li>平时Minor GC时间约在20毫秒内，但业务需要每10分钟加载一个约80MB的数据文件到内存进行数据分析，这些数据会在内存中形成超过100万个HashMap Entry，在这段时间里Minor GC会超过500毫秒，这个时间过长，GC日志如下：</li></ul><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/25/70d49060-c6e5-11e9-ad5a-c9a6da72bdb3.png" alt="example_incorr_collection1.png"><br><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/25/70d3a600-c6e5-11e9-ad5a-c9a6da72bdb3.png" alt="example_incorr_collection2.png"></p><p><strong>问题分析</strong></p><ul><li>在分析数据文件期间，800M的Eden空间在Minor GC后对象还是存活的，而ParNew垃圾收集器使用的是复制算法，把这些对象复制到Survivor并维持这些对象引用成为沉重的负担，导致GC时间变长；</li><li>从GC可以将Survivor空间去掉（加入参数-XX:SurvivorRatio&#x3D;65536、-XX:MaxTenuringThreshold&#x3D;0或者-XX:AlwaysTenure），让新生代存活的对象第一次Minor GC后立即进入老年代，等到Major GC再清理。这种方式可以治标，但也有很大的副作用。</li><li>另外一种是从程序设计的角度看，HashMap结构中，只有key和value所存放的两个长整形数据是有效数据，共16B（2 * 8B），而实际耗费的内存位88B（长整形包装为Long对象需要多8B的MarkWord、8B的Klass指针，Map.Entry多了16B的对象头、8B的next字段和4B的int型hash字段、为对齐添加的4B空白填充，另外还有8B的Entry引用），内存空间效率（18%）太低。</li></ul><h3 id="由Windows虚拟内存导致的长时间停顿"><a href="#由Windows虚拟内存导致的长时间停顿" class="headerlink" title="由Windows虚拟内存导致的长时间停顿"></a>由Windows虚拟内存导致的长时间停顿</h3><p><strong>问题描述</strong></p><ul><li>有一个带心跳检测功能的GUI桌面程序，每15秒发送一次心跳检查信号，如果对方30秒内都没有信信号返回，则认为和对方已断开连接；</li><li>程序上线后发现有误报，查询日志发现误报是因为程序会偶尔出现间隔约1分钟左右的时间完全无日志输出，处于停顿状态；</li><li>另外观察到GUI程序最小化时，资源管理中显示的占用内存大幅减小，但虚拟内存没变化；</li><li>因为是桌面程序，所需内存不大（-Xmx256m），加入参数-XX:+PrintGCApplicationStoppedTime -XX：PrintGCDateStamps -Xloggc:gclog.log后，从日志文件确认是GC导致的，大部分的GC时间在100ms以内，但偶尔会出现一次接近1min的GC；</li><li>加入参数-XX：PrintReferenceGC参数查看GC的具体日志信息，发现执行GC动作的时间并不长，但从准备开始GC到真正GC直接却消耗了大部分时间，如下所示：<br><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/25/7b018b60-c6e5-11e9-ad5a-c9a6da72bdb3.png" alt="example_virtual_memory.png"><br><strong>问题分析</strong></li><li>初步怀疑是最小化时工作内存被自动交换到磁盘的页面文件中，这样发生GC时就有可能因为恢复页面文件的操作而导致不正常的GC停顿；</li><li>在MSDN查证确认了这种猜想，加入参数-Dsun.awt.keepWorkingSetOnMinimize&#x3D;true来解决；这个参数在很多AWT程序如VisualVM都有应用。</li></ul><h2 id="实战：Eclipse运行速度调优"><a href="#实战：Eclipse运行速度调优" class="headerlink" title="实战：Eclipse运行速度调优"></a>实战：Eclipse运行速度调优</h2><ul><li>升级JDK；  </li><li>设置-XX:MaxPermSize&#x3D;256M解决Eclipse判断虚拟机版本的bug；</li><li>加入参数-Xverfify:none禁止字节码验证；</li><li>虚拟机运行在client模式，采用C1轻量级编译器；</li><li>把-Xms和-XX：PermSize参数设置为-Xmx和-XX:MaxPermSize一样，这样强制虚拟机启动时把老年代和永久代的容量固定下来，避免运行时自动扩展；</li><li>增加参数-XX：DisableExplicitGC屏蔽掉显式GC触发；</li><li>采用ParNew+CMS的垃圾收集器组合；</li><li>最终从Eclipse启动耗时15秒到7秒左右， eclipse.ini配置如下：<br><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/25/beeb8fb0-c6e5-11e9-ad5a-c9a6da72bdb3.png" alt="5376408f7d137ff833df991.png"></li></ul>]]></content>
    
    
    <summary type="html">深入理解Java虚拟机 - 第五章</summary>
    
    
    
    <category term="Java" scheme="https://newgr8player.top/categories/Java/"/>
    
    
    <category term="Java" scheme="https://newgr8player.top/tags/Java/"/>
    
    <category term="JVM" scheme="https://newgr8player.top/tags/JVM/"/>
    
    <category term="笔记" scheme="https://newgr8player.top/tags/%E7%AC%94%E8%AE%B0/"/>
    
  </entry>
  
  <entry>
    <title>深入理解Java虚拟机 - 第四章</title>
    <link href="https://newgr8player.top/posts/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Java%E8%99%9A%E6%8B%9F%E6%9C%BA---%E7%AC%AC%E5%9B%9B%E7%AB%A0/"/>
    <id>https://newgr8player.top/posts/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Java%E8%99%9A%E6%8B%9F%E6%9C%BA---%E7%AC%AC%E5%9B%9B%E7%AB%A0/</id>
    <published>2018-07-18T00:30:13.000Z</published>
    <updated>2024-12-13T07:02:02.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="第四章-虚拟机性能监控与故障处理工具"><a href="#第四章-虚拟机性能监控与故障处理工具" class="headerlink" title="第四章 虚拟机性能监控与故障处理工具"></a>第四章 虚拟机性能监控与故障处理工具</h2><h3 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h3><p>给一个系统定位问题时，知识、经验是关键基础，数据是依据，工具是运用知识处理数据的手段。</p><h3 id="JDK命令行工具"><a href="#JDK命令行工具" class="headerlink" title="JDK命令行工具"></a>JDK命令行工具</h3><h4 id="jps-虚拟机进程状况工具"><a href="#jps-虚拟机进程状况工具" class="headerlink" title="jps: 虚拟机进程状况工具"></a>jps: 虚拟机进程状况工具</h4><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/25/d06485d0-c6e5-11e9-ad5a-c9a6da72bdb3.png" alt="tools_jps_opt.png"><br><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/25/d792bfc0-c6e5-11e9-ad5a-c9a6da72bdb3.png" alt="tools_jps.png"></p><ul><li>功能：可以列出正在运行的虚拟机进程，并线上虚拟机执行的主类名称及其本地虚拟机唯一ID（LVMID）；</li><li>对于本地虚拟机来说，LVMID和操作系统的进程ID是一致的；</li><li>其他的工具通常都需要依赖jps获取LVMID；</li><li>主要选项：-q（只输出LVMID）、-m（输出传给main函数的参数）、-l（输出主类的全名）、-v（输出虚拟机启动JVM参数）；</li></ul><h4 id="jstat：虚拟机统计信息监视工具"><a href="#jstat：虚拟机统计信息监视工具" class="headerlink" title="jstat：虚拟机统计信息监视工具"></a>jstat：虚拟机统计信息监视工具</h4><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/25/df264b30-c6e5-11e9-ad5a-c9a6da72bdb3.png" alt="tools_jstat_opt.png"><br><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/25/0f686b70-c6e6-11e9-ad5a-c9a6da72bdb3.png" alt="tools_jstat.png"></p><ul><li>功能：监视虚拟机各种运行状态信息，包括类装载、内存、垃圾收集、JIT等；</li><li>纯文本监控首选；</li></ul><h4 id="jinfo：Java配置信息工具"><a href="#jinfo：Java配置信息工具" class="headerlink" title="jinfo：Java配置信息工具"></a>jinfo：Java配置信息工具</h4><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/25/19fe1350-c6e6-11e9-ad5a-c9a6da72bdb3.png" alt="tools_jinfo.png"></p><ul><li>功能：实时地查看虚拟机各项参数。虽然jps -v可以查看虚拟机启动参数，但是无法查看一些系统默认的参数。</li><li>支持运行期修改参数的能力，格式为“jinfo -flag name&#x3D;value pid”；</li></ul><h4 id="jmap：Java内存映像工具"><a href="#jmap：Java内存映像工具" class="headerlink" title="jmap：Java内存映像工具"></a>jmap：Java内存映像工具</h4><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/25/22877070-c6e6-11e9-ad5a-c9a6da72bdb3.png" alt="tools_jmap_opt.png"><br><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/25/2929be10-c6e6-11e9-ad5a-c9a6da72bdb3.png" alt="tools_jmap.png"></p><ul><li>功能：用于生成堆转储快照（一般称为heapdump或dump文件）；</li><li>其他可生成heapdump的方式：使用参数-XX:+HeapDumpOnOutOfMemoryError；使用参数-XX:+HeapDumpOnCtrlBreak然后使用Ctrl+Break生成；Linux系统使用kill -3生成；</li><li>另外它还可以查询finalize执行队列、Java堆和永久代的详细信息；</li></ul><h4 id="jhat：虚拟机堆转储快照分析工具"><a href="#jhat：虚拟机堆转储快照分析工具" class="headerlink" title="jhat：虚拟机堆转储快照分析工具"></a>jhat：虚拟机堆转储快照分析工具</h4><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/25/321140c0-c6e6-11e9-ad5a-c9a6da72bdb3.png" alt="tools_jhat.png"><br><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/25/38238360-c6e6-11e9-ad5a-c9a6da72bdb3.png" alt="tools_jhat_view.png"></p><ul><li>功能：用于分析jmap生成的heapdump。其内置了一个微型的HTTP服务器，可以在浏览器查看分析结果；</li><li>实际很少用jhat，主要有两个原因：一是分析工程会耗用服务器资源；功能相对BisualVM、IBM HeapAnalyzer较为简陋；</li></ul><h4 id="jstack：Java堆栈跟踪工具"><a href="#jstack：Java堆栈跟踪工具" class="headerlink" title="jstack：Java堆栈跟踪工具"></a>jstack：Java堆栈跟踪工具</h4><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/25/3dee5cc0-c6e6-11e9-ad5a-c9a6da72bdb3.png" alt="tools_jstack_opt.png"><br><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/25/461611e0-c6e6-11e9-ad5a-c9a6da72bdb3.png" alt="tools_jstack.png"></p><ul><li>功能：用于生成虚拟机当前时刻的线程快照（一般称为threaddump或javacore文件）。javacore主要目的是定位线程出现长时间停顿的原因，比如死锁、死循环、请求外部资源响应长等；</li><li>另外JDK 1.5后Thread类新增了getAllStackTraces()方法，可以基于此自己增加管理页面来分析；</li></ul><h4 id="HSDIS：JIT生成代码反编译"><a href="#HSDIS：JIT生成代码反编译" class="headerlink" title="HSDIS：JIT生成代码反编译"></a>HSDIS：JIT生成代码反编译</h4><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/25/4e4911a0-c6e6-11e9-ad5a-c9a6da72bdb3.png" alt="tools_hsdis.png"><br><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/25/543ecb90-c6e6-11e9-ad5a-c9a6da72bdb3.png" alt="tools_hsdis_result.png"></p><ul><li>现代虚拟机的实现慢慢地和虚拟机规范产生差距，如果要分析程序如果执行，最常见的就是通过软件调试工具（GDB、Windbg等）断点调试，但是对于Java来说，很多执行代码是通过JIT动态生成到CodeBuffer中的；</li><li>功能：HSDIS是官方推荐的HotSpot虚拟机JIT编译代码的反汇编工具，它包含在HotSpot虚拟机的源码中但没有提供编译后的程序，可以自己下载放到JDK的相关目录里；</li></ul><h3 id="JDK的可视化工具"><a href="#JDK的可视化工具" class="headerlink" title="JDK的可视化工具"></a>JDK的可视化工具</h3><h4 id="JConsole：Java监视与管理控制台"><a href="#JConsole：Java监视与管理控制台" class="headerlink" title="JConsole：Java监视与管理控制台"></a>JConsole：Java监视与管理控制台</h4><ul><li>是一种基于JMX的可视化监控和管理工具，它管理部分的功能是针对MBean进行管理，由于MBean可以使用代码、中间件服务器或者所有符合JMX规范的软件进行访问，因此这里着重介绍JConsole的监控功能；</li><li>通过jconsole命令启动JConsole后，会自动搜索本机所有虚拟机进程。另外还支持远程进程的监控；</li><li>进入主界面，支持查看以下标签页：概述、内存、线程、类、VM摘要和MBean；</li></ul><h4 id="4-3-2-VisualVM：多合一故障处理工具"><a href="#4-3-2-VisualVM：多合一故障处理工具" class="headerlink" title="4.3.2 VisualVM：多合一故障处理工具"></a>4.3.2 VisualVM：多合一故障处理工具</h4><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/25/5b8f5a90-c6e6-11e9-ad5a-c9a6da72bdb3.png" alt="tools_visualvm_2.png"></p><ul><li>目前为止JDK发布的功能最强调的运行监控和故障处理程序，另外还支持性能分析；</li><li>VisualVM还有一个很大的优点：不需要被监视的程序基于特殊Agent运行，对应用程序的实际性能影响很小，可直接应用在生成环境中；</li><li>VisualVM基于NetBeans平台开发，具备插件扩展功能的特性，基于插件可以做到：显示虚拟机进程以及进程配置、环境信息（jps、jinfo）、监视应用程序的CPU、GC、堆、方法区以及线程的信息（jstat、jstack）、dump以及分析堆转储快照（jmap、jhat）、方法级的程序运行性能分析，找出被调用最多运行时间最长的方法、离线程序快照（收集运行时配置、线程dump、内存dump等信息建立快照）、其他plugins的无限可能。</li><li>使用jvisualvm首次启动时需要在线自动安装插件（也可手工安装）；</li><li>特色功能：生成浏览堆转储快照（摘要、类、实例标签页、OQL控制台）、分析程序性能（Profiler页签可以录制一段时间程序每个方法执行次数和耗时）、BTrace动态日志跟踪（不停止目标程序运行的前提下通过HotSwap技术动态加入调试代码）；</li></ul>]]></content>
    
    
    <summary type="html">深入理解Java虚拟机 - 第四章 虚拟机性能监控与故障处理工具</summary>
    
    
    
    <category term="Java" scheme="https://newgr8player.top/categories/Java/"/>
    
    
    <category term="Java" scheme="https://newgr8player.top/tags/Java/"/>
    
    <category term="JVM" scheme="https://newgr8player.top/tags/JVM/"/>
    
    <category term="笔记" scheme="https://newgr8player.top/tags/%E7%AC%94%E8%AE%B0/"/>
    
  </entry>
  
  <entry>
    <title>深入理解Java虚拟机 - 第三章</title>
    <link href="https://newgr8player.top/posts/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Java%E8%99%9A%E6%8B%9F%E6%9C%BA---%E7%AC%AC%E4%B8%89%E7%AB%A0/"/>
    <id>https://newgr8player.top/posts/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Java%E8%99%9A%E6%8B%9F%E6%9C%BA---%E7%AC%AC%E4%B8%89%E7%AB%A0/</id>
    <published>2018-07-17T08:43:22.000Z</published>
    <updated>2024-12-13T07:02:02.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="第三章-垃圾收集器与内存分配策略"><a href="#第三章-垃圾收集器与内存分配策略" class="headerlink" title="第三章 垃圾收集器与内存分配策略"></a>第三章 垃圾收集器与内存分配策略</h2><h3 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h3><p>垃圾收集（Garbage Collection , GC）的历史远远比Java久远。它需要完成三件事：</p><ul><li>哪些内存需要回收</li><li>什么时候回收</li><li>如何回收</li></ul><p>程序计数器、虚拟机栈、本地方法栈三个区域随线程而生，随线程而灭；栈中的栈帧随着方法的进入和退出而有条不紊地执行着出栈和入栈操作，每一个栈帧需要分配多少内存基本上是在类结构确定下来时就已知的（尽管在运行期会由JIT编译器进行一些优化，但是大体上可以认为是编译器可知的），因此在这几个区域的内存分配和回收都具有确定性，这几个区域不太需要过多地考虑回收的问题。而Java堆和方法区则不一样，一个接口中的多个实现类需要的内存可能不一样，一个方法中的多个分支需要的内存也可能不一样，我们只有在程序处于运行期间才能知道会创建哪些对象，这部分的内存分配和回收都是动态的，垃圾收集器需要关注的是这部分内存，我们所讨论的“内存”分配与回收也仅仅指着一部分。</p><h3 id="对象已死？"><a href="#对象已死？" class="headerlink" title="对象已死？"></a>对象已死？</h3><p>堆中存放着Java世界中几乎所有的对象，垃圾收集器在对堆进行回收前，第一件事就是要确定哪些对象还“存活着”，哪些已经“死去”（即不可能再被任何途径使用的对象）。</p><h4 id="引用计数算法"><a href="#引用计数算法" class="headerlink" title="引用计数算法"></a>引用计数算法</h4><p><strong>引用计数算法（Reference Counting）</strong>：给对象添加一个引用计数器，每当有一个地方引用它时，计数器的数值就加1；当引用失效时，计数器数值就减1；任何时刻计数器都为0的对象就是不可能再被使用的。<br>实际上，Java并没有采用引用计数算法，因为它很难解决对象之间的相互循环引用的问题。    </p><h4 id="根搜索算法"><a href="#根搜索算法" class="headerlink" title="根搜索算法"></a>根搜索算法</h4><p>在主流的商业程序语言中（Java和C#），都是使用<strong>根搜索算法（GC Roots Tracing）</strong>来判定对象是否存活的。<br>这个算法的基本思路是：通过一系列的名字为“GC Roots”的对象作为起点，从这些节点开始向下搜索，搜索所走过的路径称为<strong>引用链（Reference Chain）</strong>，当一个对象到GC Roots没有任何引用链对象相连（用图论的话说，就是从GC Roots到这个对象不可到达）时，则证明此对象不可用。<br>如下图示：    </p><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/25/fe037d10-c6e6-11e9-ad5a-c9a6da72bdb3.jpg" alt="887319799315780bfe0ca238f6d9bb11.jpg"></p><ul><li><p>对象object5、object6和object7虽然互相关联，但是他们到GC Roots是不可到达的，所以它们会被判定为师可回收对象。<br>在Java中，可以作为GC Roots的对象有以下几种：    </p></li><li><p>虚拟机栈（栈中的本地变量表）中的引用的对象</p></li><li><p>方法区中的类静态属性引用的对象</p></li><li><p>方法区中的常量引用的对象</p></li><li><p>本地方法栈中JNI（即一般说的Native方法）的引用的对象</p></li></ul><h4 id="再谈引用"><a href="#再谈引用" class="headerlink" title="再谈引用"></a>再谈引用</h4><p>在JDK1.2之前，Java中的引用（Reference）非常狭隘：</p><p><strong>如果Reference类型的数据中存储的数值代表着另一块内存的起始地址，就称这块内存代表着一个引用。</strong></p><p>在JDK1.2之后，Java对引用的概念进行了扩充，将引用分为强引用（Strong Reference）、软引用（Soft Reference）、弱引用（Weak Reference）和虚引用（Phantom Reference）。这四种引用强度依次减弱。</p><ul><li><strong>强引用</strong>，就是在程序代码中普遍存在的，类似<code>Object obj  = new Object()</code>这类的引用，只要强引用还存在，垃圾回收器永远不会回收掉被引用的对象。</li><li><strong>软引用</strong>，用来描述一些还有用，但是并非必需的对象。对于软引用关联的对象，在系统将要发生内存溢出异常之前，将会把这些对象放进回收范围之中并进行第二次的回收。如果这次回收还是没有足够的内存，才会抛出内存溢出异常。在JDK1.2之后，提供了SoftReference类来实现软引用。</li><li><strong>弱引用</strong>，也是用来描述非必需对象的，但是它的强度要比软引用弱一些，被弱引用关联的对象只能生存到下一次垃圾回收之前。当垃圾回收器工作时，不论当前内存是否足够，都会回收掉只被弱引用关联的对象。在JDK1.2之后，提供了WeakReference来实现弱引用。</li><li><strong>虚引用</strong>，也称为幽灵引用或者幻影引用，它是最弱的一种引用关系。一个对象是否有虚引用的存在，完全不影响其生存时间，也无法通过虚引用来获取一个对象的实例。为一个对象设置虚引用的唯一目的就是希望这个对象被收集器回收时收到一个系统通知。在JDK1.2之后，提供了PhantomReference类来实现虚引用。</li></ul><h4 id="生存还是死亡？"><a href="#生存还是死亡？" class="headerlink" title="生存还是死亡？"></a>生存还是死亡？</h4><p>在根搜索算法不可达的对象，也并非是“非死不可”的，它们暂时处于“死缓”状态，要真正宣告对象的死亡，至少要经历两次标记：</p><p>如果对象在进行根搜索后发现没有与GC Roots相连接的引用链，那它就会被第一次标记并且进行一次筛选，筛选的条件是此对象是否有必要执行finalize()方法。当对象没有覆盖finalize()方法，或者finalize()方法已经被虚拟机调用过，虚拟机将这两种情况都视为“没有必要执行”。   </p><p>如果这个对象被判定为有必要执行<strong>finalize()方法</strong>，那么这个对象就会被放在名为F-Queue的队列中，并在稍后有一条由虚拟机自动建立的、低优先级的Finalizer线程去执行。这里所说的“执行”是指虚拟机会触发这个方法，但是并不承诺会保证等待它运行结束。（这样做的目的是，如果一个对象在finalize()方法中执行缓慢或者是发生了死循环，将可能会导致F-Queue里的其他对象永久处于等待状态，甚至导致整个内存回收系统崩溃）。</p><p>finalize()方法是对象逃脱死亡命运的最后一次机会，稍后GC将对F-Queue中的对象进行第二次小规模标记，如果对象要在finalize()方法中成功拯救自己，只要重新与引用链上的任何对象建立关联即可，譬如把自己（this关键字）复制给某个类变量或者对象的成员变量。</p><h4 id="回收方法区"><a href="#回收方法区" class="headerlink" title="回收方法区"></a>回收方法区</h4><p>Java虚拟机规范不要求虚拟机在方法区实现垃圾收集，而且在方法区进行垃圾收集的“性价比”一般都比较低：在堆中，尤其是在新生代中，常规应用进行一次垃圾收集一般可回收70%～95%的空间，而永久代的垃圾收集效率远低于此。</p><p>永久代的垃圾收集主要分为两部分内容：废弃常量和无用的类。</p><p><strong>回收废弃常量</strong>与回收Java堆中的对象非常类似，假如一个字符串“abc”已经进入常量池，但是当前系统没有任何一个String对象是叫做“abc”的，换句话说就是没有任何对象引用常量池的“abc”常量，也没有其他地方引用了这个字面量，如果这时候发生内存回收，而且有必要的话，这个“abc”就会被系统“请”出常量池。常量池中的其他类（接口）、方法、字段的符号引用也与此类似。    </p><p>类需要同时满足以下三个条件，才能算是<strong>“无用的”类</strong>：</p><ul><li>该类所有的实例都被回收，也就是说Java堆中已经不存在该类的所有实例。</li><li>加载该类的ClassLoader已经被回收</li><li>该类对应的 <code>java.lang.class</code> 对象没有在任何地方被引用，无法在任何地方通过反射访问该类的方法</li></ul><p>虚拟机在一个类同时满足以上三个条件时，<strong>可以</strong>对这个无用类进行回收。（ 这里说的仅仅是“<strong>可以</strong>”，而不是和对象一样，不使用了就必然会回收）。HotSpot虚拟机提供了 <code>-Xnoclassgc</code> 参数来进行控制，还可以使用 <code>-verbose:class</code> , <code>-XX:+TraceClassLoading</code> , <code>-XX:+TraceClassUnLoading</code> 查看类的加载和卸载信息。</p><p>在大量使用反射、动态代理、CGLib等bytecode框架的场景，以及动态生成JSP和OSGi这类频繁自定义ClassLoader的场景都需要虚拟机具备类卸载的功能，以保证永久代不会溢出。</p><h3 id="垃圾收集算法"><a href="#垃圾收集算法" class="headerlink" title="垃圾收集算法"></a>垃圾收集算法</h3><p>垃圾收集算法涉及到大量的程序细节，而且各个平台的虚拟机操作内存的方法又各不相同，因此本节着重介绍几种算法的思想和发展过程。</p><h4 id="标记-清除算法"><a href="#标记-清除算法" class="headerlink" title="标记-清除算法"></a>标记-清除算法</h4><p><strong>标记-清除（Mark-Sweep）算法</strong>，分为两个部分<strong>标记</strong>和<strong>清除</strong>，首先标记出所有需要回收的对象，在标记完成之后统一回收掉所有被标记的对象。</p><p>它是最基础的算法，是因为后续的算法都是基于这种思路并对其缺点进行改进而得到的。</p><p>它的主要缺点有两个：<br>1, 效率问题，标记或清除过程的效率都不高；<br>2, 空间问题，标记清除之后会产生大量的不连续的空间碎片，在以后需要分配大对象时无法找到足够的连续内存空间而不得不进行一次另一次垃圾清理。</p><h3 id="复制算法"><a href="#复制算法" class="headerlink" title="复制算法"></a>复制算法</h3><p><strong>复制（Copying）算法</strong>的出现是为了解决“标记-清除算法”的效率问题，它将可用内存按照容量划分为大小相等的两块，每次只使用其中一块。当这一块内存使用完了，就将还存活着对象复制到另外一块上面，然后再将已使用过的内存空间一次清理掉。这样就使得每一次都是对其中一块进行内存回收，内存分配时也就不用考虑内存碎片等复杂情况，只需要移动堆顶指针，按顺序分配内存即可，实现简单，运行高效。    </p><p>现在的商业虚拟机都采用这种收集算法来回收新生代，新生代中的对象绝大部分都是朝生夕死的，所以并不需要按照1:1的比例来划分内存空间，而是将内存分为一块较大的Eden空间和两块较小的Survivor，每次使用Eden和其中一块Survivor，当回收时，将Eden和Survivor中存活的对象一次性地拷贝到另一块Survivor空间，最后清理掉Eden和Survivor空间。    </p><p>HotSpot虚拟机默认的Eden和Survivor大小比例为8:1，也就是说每次新生代中可用空间为整个新生代空间的90%（80%+10%），只有10%的内存空间是被“浪费”的。当Survivor的空间不够用的时候，需要依赖其他内存（老年代）进行<strong>分配担保（Handle Promotion）</strong>。</p><h3 id="标记-整理算法"><a href="#标记-整理算法" class="headerlink" title="标记-整理算法"></a>标记-整理算法</h3><p><strong>标记-整理（Mark-Compact）算法</strong>：标记过程与“标记-清除”算法一样，但是后续步骤不是直接对可回收对象进行清理，而是让所有的存活对象都向一端移动，然后直接清理掉端边界以外的内存。</p><h3 id="分代收集算法"><a href="#分代收集算法" class="headerlink" title="分代收集算法"></a>分代收集算法</h3><p>当前商业虚拟机的垃圾收集都是采用<strong>“分代收集（Generational Collection）算法”</strong>，根据对象的存活周期的不同将内存划分为几块。    </p><p>一般是把Java堆分为<strong>新生代</strong>和<strong>老年代</strong>，这样就可以根据各个年代的特定采用最适当的收集算法。在新生代，每次垃圾收集都发现有大批对象死去，只有少量存活，那就选用复制算法，只需要付出少量存活对象的复制成本就可以完成省收集。而老年代中因为对象存活率高，没有额外的空间进行分配担保，就必须使用“标记-清理”或者“标记-整理”算法来进行回收。</p><h2 id="垃圾收集器"><a href="#垃圾收集器" class="headerlink" title="垃圾收集器"></a>垃圾收集器</h2><p>垃圾收集器是内存回收的具体实现，Java虚拟机规范对垃圾收集器的实现并没有具体规定，因此不同厂商、不同版本的虚拟机所提供的垃圾收集器可能会有很大的区别。<br>以下是 HotSpot JVM 1.6 的垃圾收集器：<br><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/25/1b4e32a0-c6e9-11e9-ad5a-c9a6da72bdb3.png" alt="106941420170722171749668485124534.png"></p><p>其中，如果两个收集器之间有连线，说明可以搭配使用。</p><h3 id="Serial-收集器"><a href="#Serial-收集器" class="headerlink" title="Serial 收集器"></a>Serial 收集器</h3><p>特点：    </p><ul><li>单线程收集器</li><li>在进行垃圾收集时，必需暂停其他所有的工作线程（打扫卫生时，必需要求房间里停止工作产生垃圾）</li><li>简单而高效，专心做垃圾收集</li><li>虚拟机运行在Client模式下的默认新生代收集器</li></ul><h3 id="ParNew-收集器"><a href="#ParNew-收集器" class="headerlink" title="ParNew 收集器"></a>ParNew 收集器</h3><p>ParNew收集器其实就是Serial收集器的多线程版本，是运行在Server模式下的虚拟机中首选的新生代收集器。</p><p><em>并行（Parallel）：指多条垃圾收集线程并行工作，但此时用户线程处于等待状态。</em></p><p><em>并发（Concurrent）：指用户线程与垃圾收集线程同时执行（但不一定是并行的，可能会交替运行），用户程序继续运行，而垃圾收集线程运行在另一个CPU上。</em></p><h3 id="Parallel-Scavenge"><a href="#Parallel-Scavenge" class="headerlink" title="Parallel Scavenge"></a>Parallel Scavenge</h3><p>Parallel Scavenge收集器是一个使用复制算法的并行的多线程收集器，它关注于提高吞吐量（Throughput，CPU用于运行用户代码的时间和CPU消耗时间的比值）。另外，自适应调节策略也是Parallel Scavenge和ParNew收集器的区别</p><h3 id="Serial-Old-收集器"><a href="#Serial-Old-收集器" class="headerlink" title="Serial Old 收集器"></a>Serial Old 收集器</h3><p>是Serial收集器的老年版本。</p><h3 id="Parallel-Old-收集器"><a href="#Parallel-Old-收集器" class="headerlink" title="Parallel Old 收集器"></a>Parallel Old 收集器</h3><p>是Parallel Scavenge 收集器的老年版本。</p><h3 id="CMS-收集器"><a href="#CMS-收集器" class="headerlink" title="CMS 收集器"></a>CMS 收集器</h3><p>CMS（Concurrent Mask Sweep）收集器是一个以获取最短回收停顿时间为目标的收集器。因此，此收集器特别适合现代的互联网或 B&#x2F;S 架构的服务端上。    </p><p>CMS 收集器是基于“标记-清除”算法实现的，整个过程分为4个步骤：    </p><ul><li>初始标记</li><li>并发标记</li><li>重新标记</li><li>并发清除</li></ul><p>它是一种优秀的收集器。</p><ul><li>优点是：并发收集、低停顿</li><li>缺点是：对CPU资源非常敏感、无法处理浮动垃圾、收集结束后会产生大量的空间碎片以致于在给大对象分配空间时带来麻烦</li></ul><h3 id="G1-收集器"><a href="#G1-收集器" class="headerlink" title="G1 收集器"></a>G1 收集器</h3><p>G1（Garbage First）收集器是当前收集器技术发展的最新成果，相对于上文的 CMS 收集器有两个显著改进：</p><ol><li>基于“标记-整理”算法，也就是说它不会产生空间碎片</li><li>非常精确地控制停顿</li></ol><p>G1 收集器可以实现在基本不牺牲吞吐量的情况下完成低停顿的回收，它将整个Java堆划分为多个大小固定的独立区域（Region），并跟踪这些区域里面的垃圾堆积程度，在后台维护一个优先列表，每次根据允许的收集时间，优先回收垃圾最多的区域（这也是Garbage First名称的由来）。总而言之，区域划分和有优先级的区域回收，保证了G1收集器在有限的时间内可以获得最高的收集效率。</p><h3 id="垃圾收集器参数总结"><a href="#垃圾收集器参数总结" class="headerlink" title="垃圾收集器参数总结"></a>垃圾收集器参数总结</h3><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></pre></td><td class="code"><pre><span class="line">-XX:+UseSerialGC：在新生代和老年代使用串行收集器</span><br><span class="line">-XX:SurvivorRatio：设置eden区大小和survivior区大小的比例</span><br><span class="line">-XX:NewRatio:新生代和老年代的比</span><br><span class="line">-XX:+UseParNewGC：在新生代使用并行收集器</span><br><span class="line">-XX:+UseParallelGC ：新生代使用并行回收收集器</span><br><span class="line">-XX:+UseParallelOldGC：老年代使用并行回收收集器</span><br><span class="line">-XX:ParallelGCThreads：设置用于垃圾回收的线程数</span><br><span class="line">-XX:+UseConcMarkSweepGC：新生代使用并行收集器，老年代使用CMS+串行收集器</span><br><span class="line">-XX:ParallelCMSThreads：设定CMS的线程数量</span><br><span class="line">-XX:CMSInitiatingOccupancyFraction：设置CMS收集器在老年代空间被使用多少后触发</span><br><span class="line">-XX:+UseCMSCompactAtFullCollection：设置CMS收集器在完成垃圾收集后是否要进行一次内存碎片的整理</span><br><span class="line">-XX:CMSFullGCsBeforeCompaction：设定进行多少次CMS垃圾回收后，进行一次内存压缩</span><br><span class="line">-XX:+CMSClassUnloadingEnabled：允许对类元数据进行回收</span><br><span class="line">-XX:CMSInitiatingPermOccupancyFraction：当永久区占用率达到这一百分比时，启动CMS回收</span><br><span class="line">-XX:UseCMSInitiatingOccupancyOnly：表示只在到达阀值的时候，才进行CMS回收</span><br></pre></td></tr></table></figure><h2 id="内存分配与回收策略"><a href="#内存分配与回收策略" class="headerlink" title="内存分配与回收策略"></a>内存分配与回收策略</h2><p>Java 技术体系中的自动内存管理最终可以可以归结为自动化地解决了以下两个问题：<strong>给对象分配内存</strong>和<strong>回收分配给对象的内存</strong>。    </p><p>其中，关于<strong>回收内存</strong>是上文介绍的虚拟机中垃圾收集体系及其工作原理所阐述的内容。</p><p>而，关于<strong>分配内存</strong>则是本节需要阐述的内容。</p><h3 id="对象优先在Eden分配"><a href="#对象优先在Eden分配" class="headerlink" title="对象优先在Eden分配"></a>对象优先在Eden分配</h3><p>大多数情况下，对象在新生代Eden区中分配。当Eden区没有足够的空间进行分配时，虚拟机将发起一次Minor GC。</p><ul><li><strong>新生代GC（Minor GC）</strong>：指发生在新生代的垃圾收集动作，因为Java对象大多数都具有朝生夕灭的特性，所以Minor GC非常频繁，一般回收速度也比较快。</li><li><strong>老年代GC（Major GC／Full GC）</strong>：指发生在老年代的GC，出现了Major GC，经常会伴随至少发生一次的Minor GC（但是并非绝对，在ParallelScavenge收集器的收集策略里，就有直接进行Major GC的策略选择过程）。Major GC的速度一般会比Minor GC慢10倍以上。</li></ul><h3 id="大对象直接进入老年代"><a href="#大对象直接进入老年代" class="headerlink" title="大对象直接进入老年代"></a>大对象直接进入老年代</h3><p>大对象，是指需要大量连续内存空间的Java对象，最典型的大对象就是那种很长的字符串及数组。大对象对于虚拟机的内存分配来说，是一个坏消息，因为它的经常出现容易导致内存还有不少空间时就提前触发垃圾收集以获取足够的连续空间来“安置”它们。</p><p>虚拟机提供了<code>-XX:PretenureSizeThreshold</code>参数，让大于这个设置值的对象直接在老年代中分配，这样做的目的是避免在Eden区及两个Survivor区之间发生大量的内存拷贝（新生代采用复制算法来收集内存）。</p><p><em>PretenureSizeThreshold参数只对Serial和ParNew两款收集器有效，Parallel Scavenge收集器不认识这个参数，一般也没必要设置。如果遇到必需使用此参数的场合，可以考虑ParNew+CMS的收集器组合</em></p><h3 id="长期存活的对象进入老年代"><a href="#长期存活的对象进入老年代" class="headerlink" title="长期存活的对象进入老年代"></a>长期存活的对象进入老年代</h3><p>虚拟机采用了分代收集的思想来管理内存，那么回收时就必须能够识别哪些对象应当放在新生代，哪些对象应该放在老年代。为了达到这个目的，虚拟机给每个对象定义了一个对象年龄（Age）计数器。</p><p>如果对象在Eden出现并经过第一次MinorGC后仍然存活，并且能被Survivor容纳，将被移动到Survivor空间，对象年龄加1。对象在Survivor区每熬过一次Minor GC，年龄就增加1岁，当它的年龄增加到一定程度（默认值是15）时，就会被晋升到老年代中。</p><p><em>对象晋升老年代的年龄阈值，可以通过参数<code>-XX:MaxTenuringThreshold</code>来设置</em></p><h3 id="动态对象年龄判定"><a href="#动态对象年龄判定" class="headerlink" title="动态对象年龄判定"></a>动态对象年龄判定</h3><p>为了能够更好地适应不同程序的内存状况，虚拟机并不是总要求对象的年龄必需达到MaxTenuringThreshold 才能晋升老年代，如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半，年龄大于或等于该年龄的对象就可以直接进入老年代，无须等到MaxTenuringThreshold 中要求的年龄。</p><h3 id="空间分配担保"><a href="#空间分配担保" class="headerlink" title="空间分配担保"></a>空间分配担保</h3><p>在发生Minor GC时，虚拟机会检测之前每次晋升到老年代的平均大小是否大于老年代的剩余空间大小，如果大于，则改为直接进行一次Full GC。如果小于，则查看HandlePromotionFailure设置是否允许担保失败；如果允许，那只会进行Minor GC；如果不允许，则也要改为进行一次Full GC。</p>]]></content>
    
    
    <summary type="html">深入理解Java虚拟机 - 第三章 垃圾收集器与内存分配策略</summary>
    
    
    
    <category term="Java" scheme="https://newgr8player.top/categories/Java/"/>
    
    
    <category term="Java" scheme="https://newgr8player.top/tags/Java/"/>
    
    <category term="JVM" scheme="https://newgr8player.top/tags/JVM/"/>
    
    <category term="笔记" scheme="https://newgr8player.top/tags/%E7%AC%94%E8%AE%B0/"/>
    
  </entry>
  
  <entry>
    <title>深入理解Java虚拟机 - 第二章</title>
    <link href="https://newgr8player.top/posts/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Java%E8%99%9A%E6%8B%9F%E6%9C%BA---%E7%AC%AC%E4%BA%8C%E7%AB%A0/"/>
    <id>https://newgr8player.top/posts/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Java%E8%99%9A%E6%8B%9F%E6%9C%BA---%E7%AC%AC%E4%BA%8C%E7%AB%A0/</id>
    <published>2018-07-17T08:22:49.000Z</published>
    <updated>2024-12-13T07:02:02.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="第二章-Java内存区域、内存溢出异常"><a href="#第二章-Java内存区域、内存溢出异常" class="headerlink" title="第二章 Java内存区域、内存溢出异常"></a>第二章 Java内存区域、内存溢出异常</h2><h3 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h3><p>Java与C++之间有一堵由<strong>内存分配</strong>和<strong>垃圾收集</strong>技术所围成的高墙，墙外的人想进来，墙里面的人想出来。</p><h3 id="Java运行时数据区"><a href="#Java运行时数据区" class="headerlink" title="Java运行时数据区"></a>Java运行时数据区</h3><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/25/4846a620-c6e9-11e9-ad5a-c9a6da72bdb3.png" alt="Java运行时数据区"></p><h4 id="程序计数器"><a href="#程序计数器" class="headerlink" title="程序计数器"></a>程序计数器</h4><p><strong>程序计数器（Program Counter Register）</strong>是一块儿较小的空间，用来指示当前线程所执行字节码的行号。 </p><p>字节码解释器工作时就是通过改变这个计数器的值来选定下一条需要执行的字节码指令，分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。 </p><p><em>Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的</em>。在任一时刻，一个处理器（对于多核处理器来说，就是一个内核）只会执行一条线程中的指令。因此，为了能够恢复线程切换之前的线程状态到正确位置，每条线程都需要有一个独立的程序计数器，各条线程之间互不影响，独立存储，我们称这类内存区域为<strong>“线程私有”</strong>的内存。    </p><p>如果线程正在执行的是一个Java方法，这个计数器记录的是正在执行虚拟机字节码指令的地址；如果正在执行的是一个Native方法，这个计数器则是为空（Undefined）。<br>此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。    </p><h4 id="虚拟机栈"><a href="#虚拟机栈" class="headerlink" title="虚拟机栈"></a>虚拟机栈</h4><p><strong>Java虚拟机栈（Java Virtual Machine Stack）</strong> 也是线程私有的，它的生命周期与线程相同。它描述的是Java方法执行的内存模型：每个方法执行的时候都会同时创建一个栈帧（Stack Frame）用于存储局部变量、操作栈、动态链接、方法出口等信息。<br><em>每一个方法被调用直至执行完成的过程，就对应着一个栈帧从虚拟机栈中从入栈到出栈的过程。</em><br>有人把Java内存区分为栈（Stack）和堆（Heap），这是比较粗糙的。实际上，这里的“栈”指的就是现在的这个虚拟机栈，或者确切点说，是虚拟机栈中的局部变量表部分。<br>局部变量表中存放了编译期可知的基本数据类型（boolean、byte、char、short、int、float、long、double）、对象引用（refrence类型，可能是一个对象起始地址的应用指针、也可能是一个代表对象的句柄或者其它与此对象相关的地址）和returnAddress（指向了一条字节码指令的地址）。<br>其中，64位长度的long和double类型的数据会占用2个局部变量空间（Slot），其余数据类型会占用一个。局部变量所占用的空间在编译期间完成分配，当进入一个方法时，这个方法在帧中所需要分配的局部变量的空间时完全可以确定的，在运行期间也不会改变这个空间的大小。<br>如果线程请求的栈的深度大于虚拟机所能允许的深度，会触发StackOverflowError异常；如果虚拟机可以动态地扩展（当前大部分虚拟机都可以扩展，也可以指定固定长度），当扩展无法申请到足够的内存时，会抛出OutOfMemoryError异常。    </p><h4 id="本地方法栈"><a href="#本地方法栈" class="headerlink" title="本地方法栈"></a>本地方法栈</h4><p><strong>本地方法栈（Native Method Stack）</strong> 与虚拟机栈的作用非常相似，只不过虚拟机栈是为虚拟机执行字节码（Java方法）服务，而本地方法栈是用来服务于虚拟机使用的Native方法服务。<br>具体的虚拟机可以自由实现它，甚至可以把它和虚拟机栈合二为一（譬如Sun HotSpot 虚拟机）。<br>与虚拟机栈一样，本地方法栈也会抛出StackOverflowError和OutOfMemoryErroe异常。    </p><h4 id="Java堆"><a href="#Java堆" class="headerlink" title="Java堆"></a>Java堆</h4><p><strong>Java堆（Java Heap）</strong> 是Java虚拟机所管理的内存中最大的一块。是被所有的线程共享的一块内存区域，在虚拟机启动时创建。</p><p>此内存区域的唯一目的就是存放对象实例，几乎所有的对象实例都在此分配内存。由于JIT编译器的发展与逃逸分析技术的发展，栈上分配、标量替换优化技术的进步，所有的对象实例都分配在堆上也不是那么绝对了。 </p><p><em>Java堆是垃圾收集器管理的主要区域</em> ，因此也被称为“GC堆”（Garbage Collection Heap）。现在的收集器都是采用分代收集算法，所以Java堆中可以细分为：新生代和老生代。</p><p>根据Java虚拟机规范，Java堆可以是处于物理上不连续的内存空间中，只要逻辑上连续即可。在实现上可以是固定大小，也可以是可扩展的，主流的是可扩展方式。</p><h3 id="方法区"><a href="#方法区" class="headerlink" title="方法区"></a>方法区</h3><p><strong>方法区（Method Area）</strong> 与Java堆一样，都是各个线程共享的内存区域，它用于存储已被虚拟机加载的类信息、常量、静态常量、即时编译器编译后的代码等数据。</p><p>Java虚拟机规范对这个区域的限制非常松，除了和Java堆一样不需要连续的内存空间和可以选择固定大小或者可扩展外，还可以选择不实现垃圾收集。</p><p>当方法区的内存大小无法满足内存分配需要的时候，将抛出OutOfMemory异常。</p><h3 id="运行时常量池"><a href="#运行时常量池" class="headerlink" title="运行时常量池"></a>运行时常量池</h3><p><strong>运行时常量池（Runtime Constant Pool）</strong>是方法区的一部分。Class文件除了有类的版本、字段、方法、接口等描述信息，还有一项信息就是常量池，用于存放编译时期生成的各种字面量和符号引用，这部分内容将在类加载后存放到方法区的运行时常量池。</p><p>运行时常量池相对于Class文件常量池的另外一个重要特征是<strong>具备动态性</strong>，Java语言并不要求常量一定只能在编译期产生，也就是并非预置入Class文件中常量池的内容才能进入方法区运行时常量池，运行期间也可能将将新的常量放入池中，这种特性被开发人员利用的比较多的便是String类的intern()方法。</p><p>既然运行时常量池是方法区的一部分，自然会受到方法区内存的限制，当常量池无法再申请到内存时将会抛出OutOfMemoryError异常。</p><h3 id="直接内存"><a href="#直接内存" class="headerlink" title="直接内存"></a>直接内存</h3><p><strong>直接内存（Direct Memory）</strong> 并不是虚拟机运行时数据区的一部分，也不是Java虚拟机规范中定义的内存区域，但是这部分内存也被频繁地使用，而且有可能导致OutOfMemoryError异常。</p><p>在JDK1.4种新加入了NIO（New Iuput&#x2F;Output）类，引入了一种基于通道（Channel）与缓冲区（Buffer）的I&#x2F;O方式，它可以使用Native函数直接分配堆外内存，然后通过一个存储在Java堆里面的DirectByteBuffer对象作为这快内存的引用进行操作。这样能在一些场景中显著提高性能，因为避免了在Java堆和Native堆中来回复制数据。</p><p>显然，本机直接内存非分配不会受到Java堆大小的限制，但是既然是内存，肯定会受到本机总内存的大小和处理器寻址空间的限制。所以也可能会抛出OutOfMemoryError异常。</p><h3 id="对象访问"><a href="#对象访问" class="headerlink" title="对象访问"></a>对象访问</h3><p>对象访问在Java语言中无处不在，是最普通的程序行为，即使是最简单的访问，也涉及到Java栈、Java堆、方法区这三个重要的内存区域之间的关联关系，如下一句代码：  </p><pre><code>```Object obj = new Object();```    </code></pre><p>假设这句代码出现在方法体中，那”Object obj”这部分的语义将会反映到Java栈的本地变量中，作为一个Reference类型数据出现。    </p><p>而”new Object()”这部分的语义将会反映到Java堆中，形成一块儿存储了Object类型所有实例数据值（Instance Data，对象中各个实例字段的数据）的结构化内存，根据具体类型以及虚拟机实现的对象内存布局（Object Memory Layout）的不同，这块儿内存的大小是不固定的。 </p><p>另外，在Java堆中，还必须包含能包括能查到此对象类型数据（如，对象类型、父类、实现的接口、方法等）的地址信息，这些数据存储在方法区中。 </p><p>由于reference类型在Java虚拟机规范里只规定了一个指向对象的引用，并没有定义这个引用应该通过哪种方式去定位，以及访问到Java堆中的对象的具体位置，因此不同的虚拟机实现的对象访问方式会有所不同。</p><p>主流的访问方式有两种：使用句柄和直接指针。</p><ul><li><strong>句柄方式</strong>，Java堆中将会划分出一块内存作为句柄池，reference中存储的就是对象的句柄地址，而句柄中包含了对象实例数据和类型数据各自的具体地址信息，如下图：</li></ul><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/25/d5334f20-c6e9-11e9-ad5a-c9a6da72bdb3.png" alt="句柄方式访问对象.png"></p><ul><li><strong>指针方式</strong>，reference变量中直接存储的就是对象的地址，而Java堆对象的布局中就必须考虑如何防止访问类型数据的相关信息，如下图：</li></ul><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/25/e4717070-c6e9-11e9-ad5a-c9a6da72bdb3.png" alt="指针方式访问对象.png"></p><p>这两种对象的访问方式各有优势：    </p><ul><li>使用句柄访问方式的最大好处是reference中存贮的是稳定的句柄地址，在对象被移动（比如垃圾收集时，对象移动是非常普遍的行为）时只会改变句柄中的实例数据指针，而reference本身不需要被修改。    </li><li>使用直接指针访问方式的最大好处就是速度更快，它节省了一次指针定位的时间开销，由于对象的访问在Java中非常频繁，因此这类开销积少成多也是一项非常可观的执行成本。</li><li><em>本书讨论的主要虚拟机Sun HotSpot，它是使用第二种方式进行对象访问的，但是从整个软件开发的范围来看，各种语言和框架使用句柄来访问的情况也十分常见。</em></li></ul><h2 id="实战：OutOfMemoryError异常"><a href="#实战：OutOfMemoryError异常" class="headerlink" title="实战：OutOfMemoryError异常"></a>实战：OutOfMemoryError异常</h2><h3 id="Java堆溢出"><a href="#Java堆溢出" class="headerlink" title="Java堆溢出"></a>Java堆溢出</h3><p>可以通过限制Java堆的大小，设置为不可扩展（将堆的最小值参数-Xms和最大值参数-Xmx设置成一样即可避免堆自动扩展），通过参数-XX:+HeapDumpOnOutOfMemoryError可以让虚拟机在出现内存溢出异常时Dump出当前的内存堆转储快照以便以后进行分析。<br>Java堆内存的OOM异常是实际应用中最常见的内存溢出异常情况。出现Java堆内存溢出时，异常堆栈信息“java.lang.OutOfMemoryError”会跟着进一步提示“Java heap space”。<br>要解决这个区域的异常，一般的手段是首先通过内存映像分析工具（如Eclipse Memory Analyzer）对dump出的堆转储快照进行分析，重点是确认内存中的对象是否是必要的，也就是要先分清楚到底是出现了内存泄漏（Memory Leak）还是内存溢出（Memory Overflow）。<br>如果是内存泄漏，可进一步通过工具查看泄漏对象到GC Roots的引用链。于是就能找到泄漏对象是通过怎样的路径与GC Roots相关联并导致垃圾收集器无法自动回收它们的。掌握了泄漏对象的类型信息，以及GC Roots引用链的信息，就可以比较准确地定位出泄漏代码的位置。<br>如果不存在泄漏，换句话说就是内存中的对象确实都还必须存活着，那就应当检查虚拟机的堆参数（Xmx和Xms），与机器物理内存对比看是否还可以调大，从代码上检查是否存在某些对象生命周期过长、持有状态时间过长的情况，尝试减少程序运行期间的内存消耗。</p><h3 id="虚拟机栈和本地方法栈溢出"><a href="#虚拟机栈和本地方法栈溢出" class="headerlink" title="虚拟机栈和本地方法栈溢出"></a>虚拟机栈和本地方法栈溢出</h3><p>由于在HotSpot虚拟机中并不区分虚拟机栈和本地方法栈，因此对于HotSpot来说，-Xoss参数（设置本地方法栈大小）虽然存在，但实际上是无效的，栈容量只由-Xss参数设定。<br>关于虚拟机栈和本地方法栈，在Java虚拟机规范中描述了两种异常：    </p><ul><li>如果线程请求的栈深度大于虚拟机所允许的最大深度，将抛出StackOverflowError异常。</li><li>如果虚拟机在扩展栈时无法申请到足够的内存空间，则抛出OutOfMemoryError异常。</li></ul><p>这里把异常分为两种情况，实际上却存在着一些互相重叠的地方：当栈空间无法分配时，到底是内存太小，还是已使用的栈空间太大，其本质只是对同一事件的两种表述而已。</p><h3 id="运行时常量池溢出"><a href="#运行时常量池溢出" class="headerlink" title="运行时常量池溢出"></a>运行时常量池溢出</h3><p>如果要向运行时常量池中添加内容，最简单的方法是使用String.intern()这个Native方法。该方法的作用时：如果池中包含了一个等于此String对象的字符串，则返回代表池中这个字符串的String对象；否则，将此String对象包含的字符串添加到常量池中，并且返回此String对象的引用。由于常量池分配在方法区内，我们可以通过-XX:PermSize和-XX:MaxPermSize限制方法区的大小，从而间接限制其中常量池的容量。</p><h3 id="方法区溢出"><a href="#方法区溢出" class="headerlink" title="方法区溢出"></a>方法区溢出</h3><p>方法区用于存放Class相关的信息，如类名、访问修饰符、常量池、字段描述、方法描述等。生成大量的动态类区填充方法区，可以触发此区域的溢出异常。<br>方法区溢出是一种常见的内存溢出异常，一个类如果要被垃圾收集器回收，判定的条件比较苛刻。在经常动态生成大量Class的应用中，需要特别注意类的回收状况。这类场景出了CGLib字节码增强外，还有大量JSP或者基于OSGi的应用等。</p><h3 id="本机直接内存溢出"><a href="#本机直接内存溢出" class="headerlink" title="本机直接内存溢出"></a>本机直接内存溢出</h3><p>DirectMemory可以通过-XX:MaxDirectMemorySize指定，如果不指定，则默认与Java堆的最大值（-Xmx指定）一样。</p>]]></content>
    
    
    <summary type="html">深入理解Java虚拟机 - 第二章 Java内存区域、内存溢出异常</summary>
    
    
    
    <category term="Java" scheme="https://newgr8player.top/categories/Java/"/>
    
    
    <category term="Java" scheme="https://newgr8player.top/tags/Java/"/>
    
    <category term="JVM" scheme="https://newgr8player.top/tags/JVM/"/>
    
    <category term="笔记" scheme="https://newgr8player.top/tags/%E7%AC%94%E8%AE%B0/"/>
    
  </entry>
  
  <entry>
    <title>Docker基础环境搭建</title>
    <link href="https://newgr8player.top/posts/Docker%E5%9F%BA%E7%A1%80%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA/"/>
    <id>https://newgr8player.top/posts/Docker%E5%9F%BA%E7%A1%80%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA/</id>
    <published>2018-05-17T08:14:42.000Z</published>
    <updated>2024-12-13T07:02:02.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="开始安装"><a href="#开始安装" class="headerlink" title="开始安装"></a>开始安装</h1><ul><li>由于apt官方库里的docker版本可能比较旧，所以先卸载可能存在的旧版本</li></ul><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-get remove docker docker-engine docker-ce docker.io</span><br></pre></td></tr></table></figure><ul><li>添加<code>backports</code>源</li></ul><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"><span class="meta prompt_"># </span><span class="language-bash">编辑/etc/apt/sources.list文件,加入这句话</span></span><br><span class="line">deb http://http.debian.net/debian jessie-backports main</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">或者</span></span><br><span class="line">sudo add-apt-repository &quot;deb http://http.debian.net/debian jessie-backports main&quot;</span><br></pre></td></tr></table></figure><ul><li>更新<code>apt-get</code>索引</li></ul><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-get update</span><br></pre></td></tr></table></figure><ul><li>安装Docker</li></ul><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-get install docker.io</span><br></pre></td></tr></table></figure><ul><li>测试Docker安装结果</li></ul><p><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">sudo docker images ls</span><br></pre></td></tr></table></figure><h1 id="使用非root用户运行Docker"><a href="#使用非root用户运行Docker" class="headerlink" title="使用非root用户运行Docker"></a>使用非root用户运行Docker</h1><ul><li>将当前用户加入<code>docker</code>用户组</li></ul><p><strong>指定其他用户时只需要将<code>$&#123;USER&#125;</code>改为对应用户名即可</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">sudo gpasswd -a $&#123;USER&#125; docker</span><br></pre></td></tr></table></figure><ul><li>重启Docker(命令在基于Debian的发布版上大都可用)</li></ul><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 restart docker</span><br></pre></td></tr></table></figure><ul><li>当前用户注销后再重新登录</li></ul><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"><span class="meta prompt_"># </span><span class="language-bash">注销</span></span><br><span class="line">logout</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;开始安装&quot;&gt;&lt;a href=&quot;#开始安装&quot; class=&quot;headerlink&quot; title=&quot;开始安装&quot;&gt;&lt;/a&gt;开始安装&lt;/h1&gt;&lt;ul&gt;
&lt;li&gt;由于apt官方库里的docker版本可能比较旧，所以先卸载可能存在的旧版本&lt;/li&gt;
&lt;/ul&gt;
&lt;figure</summary>
      
    
    
    
    <category term="Docker" scheme="https://newgr8player.top/categories/Docker/"/>
    
    
    <category term="Linux" scheme="https://newgr8player.top/tags/Linux/"/>
    
    <category term="Docker" scheme="https://newgr8player.top/tags/Docker/"/>
    
  </entry>
  
  <entry>
    <title>Vue简单入门</title>
    <link href="https://newgr8player.top/posts/Vue%E7%AE%80%E5%8D%95%E5%85%A5%E9%97%A8/"/>
    <id>https://newgr8player.top/posts/Vue%E7%AE%80%E5%8D%95%E5%85%A5%E9%97%A8/</id>
    <published>2018-03-26T02:30:48.000Z</published>
    <updated>2024-12-13T07:02:02.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="基本结构"><a href="#基本结构" class="headerlink" title="基本结构"></a>基本结构</h1><ul><li>template</li></ul><p><strong>基本HTML语法</strong></p><ul><li>script</li></ul><p><strong>实例化的Vue对象</strong></p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> sth = <span class="keyword">new</span> <span class="title class_">Vue</span>(&#123;&#125;);</span><br></pre></td></tr></table></figure><ul><li>css</li></ul><p><strong>样式</strong></p><h1 id="Vue对象基本结构"><a href="#Vue对象基本结构" class="headerlink" title="Vue对象基本结构"></a>Vue对象基本结构</h1><ul><li>data</li></ul><p><strong>数据域</strong></p><ul><li>methods</li></ul><p><strong>用来定义功能函数</strong></p><ul><li>watch</li></ul><p><strong>用来定义数据监视函数</strong><br><strong>Demo如下:</strong></p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">new</span> <span class="title class_">Vue</span>(&#123;</span><br><span class="line">    <span class="attr">data</span>:&#123;</span><br><span class="line">        <span class="attr">a</span>:<span class="number">1</span>,</span><br><span class="line">        <span class="attr">b</span>:[]</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">props</span>: [ <span class="string">&#x27;注册组件名称&#x27;</span> ]，</span><br><span class="line">    <span class="attr">methods</span>:&#123;</span><br><span class="line">        <span class="attr">doSomething</span>:<span class="keyword">function</span>(<span class="params"></span>) &#123;</span><br><span class="line">            <span class="comment">/* 调用此函数时候打印**a**的值 */</span></span><br><span class="line">            <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>.<span class="property">a</span>)</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">watch</span>:&#123;</span><br><span class="line">        <span class="string">&#x27;a&#x27;</span> : <span class="keyword">function</span>(<span class="params">val,oldVal</span>) &#123;</span><br><span class="line">            <span class="comment">/* 当**a**的值发生改变时，调用该方法，打印**a**的旧值与新值 */</span></span><br><span class="line">            <span class="variable language_">console</span>.<span class="title function_">log</span>(val,oldVal)</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><h1 id="模版指令"><a href="#模版指令" class="headerlink" title="模版指令"></a>模版指令</h1><h2 id="数据渲染"><a href="#数据渲染" class="headerlink" title="数据渲染"></a>数据渲染</h2><p><strong>v-text</strong> &amp;&amp; <strong>v-html</strong> &amp;&amp; <strong></strong></p><figure class="highlight html"><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="tag">&lt;<span class="name">p</span>&gt;</span>&#123;&#123; a &#125;&#125;<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">p</span> <span class="attr">v-text</span>=<span class="string">&quot;a&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">p</span> <span class="attr">v-html</span>=<span class="string">&quot;a&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br></pre></td></tr></table></figure><p><strong>脚本部分</strong></p><figure class="highlight javascript"><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">new</span> <span class="title class_">Vue</span>(&#123;</span><br><span class="line">    data : &#123;</span><br><span class="line">        <span class="attr">a</span>: <span class="number">1</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><strong>v-if</strong> &amp;&amp; <strong>v-show</strong><br><strong>区别</strong><br><strong>v-if</strong>：为假时直接不渲染该块；<br><strong>v-show</strong>：类似于CSS中display:none;</p><figure class="highlight html"><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="tag">&lt;<span class="name">p</span> <span class="attr">v-if</span>=<span class="string">&quot;isShow&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">p</span> <span class="attr">v-show</span>=<span class="string">&quot;isShow&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br></pre></td></tr></table></figure><p><strong>脚本部分</strong></p><figure class="highlight javascript"><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">new</span> <span class="title class_">Vue</span>(&#123;</span><br><span class="line">data : &#123;</span><br><span class="line">   <span class="attr">isShow</span>: <span class="literal">true</span></span><br><span class="line">&#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><h2 id="渲染循环列表"><a href="#渲染循环列表" class="headerlink" title="渲染循环列表"></a>渲染循环列表</h2><p><strong>v-for</strong></p><figure class="highlight html"><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">ul</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">li</span> <span class="attr">v-for</span>=<span class="string">&quot;item in items&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">p</span> <span class="attr">v-text</span>=<span class="string">&quot;item.label&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">li</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">ul</span>&gt;</span></span><br></pre></td></tr></table></figure><p><strong>脚本部分</strong></p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">new</span> <span class="title class_">Vue</span>(&#123;</span><br><span class="line">data : &#123;</span><br><span class="line">    items : &#123;</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="attr">label</span>: <span class="string">&#x27;apple&#x27;</span></span><br><span class="line">        &#125;,</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="attr">label</span>: <span class="string">&#x27;banana&#x27;</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><h2 id="事件绑定"><a href="#事件绑定" class="headerlink" title="事件绑定"></a>事件绑定</h2><p><strong>v-on</strong> &lt;&#x3D;&#x3D;&gt;  <strong>@</strong></p><figure class="highlight html"><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="tag">&lt;<span class="name">button</span> <span class="attr">v-on:click</span>=<span class="string">&quot;doThis&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">button</span> @<span class="attr">click</span>=<span class="string">&quot;doThis&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br></pre></td></tr></table></figure><figure class="highlight javascript"><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="attr">methods</span>: &#123;</span><br><span class="line">    <span class="attr">doThis</span>: <span class="keyword">function</span>(<span class="params">sth</span>) &#123;</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><strong>v-bind</strong> &lt;&#x3D;&#x3D;&gt; <strong>:</strong></p><figure class="highlight html"><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="comment">&lt;!-- imgSrc 为字符串 --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">img</span> <span class="attr">v-bind:src</span>=<span class="string">&quot;imgSrc&quot;</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">&lt;!-- red 表示名为 red 的CSS样式（.red），isRed是data域中的布尔值，根据真假确定该样式是否渲染 --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">:class</span>&quot;&#123; <span class="attr">red</span> <span class="attr">:</span> <span class="attr">isRed</span> &#125;&quot;&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="comment">&lt;!-- 包含两种CSS样式（.classA &amp;&amp; .classB） --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">:class</span>&quot;[<span class="attr">classA</span>, <span class="attr">classB</span>]&quot;&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="comment">&lt;!-- 组合 --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">:class</span>&quot;[<span class="attr">classA</span>, &#123;<span class="attr">classB:</span> <span class="attr">isB</span>, <span class="attr">classC:</span> <span class="attr">isC</span>&#125;]&quot;&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br></pre></td></tr></table></figure><h1 id="组件化"><a href="#组件化" class="headerlink" title="组件化"></a>组件化</h1><h2 id="组件的引入与注册"><a href="#组件的引入与注册" class="headerlink" title="组件的引入与注册"></a>组件的引入与注册</h2><ul><li>引入组件后使用前需要注册，使用<strong>components</strong></li></ul><blockquote><p>注册组件后使用时候将驼峰转为中线形式原因：<br>ComponentA之所以要转换成component-a<br>因为在HTML中，标签的标签名是大小写不敏感的，而在javascript中变量名是大小写敏感的<br>换句话说，在HTML中，ComponentA和componenta算是同一个标签<br>而在javascript中，ComponentA和componenta却不是同一个变量<br>要解决这种差异问题，就必须得在两种标准之间做一个转换，所以vuejs就使用了将驼峰法(camelCase)转换到短横线法(kebab-case)</p></blockquote><ul><li>父向子组件传值<strong>props</strong>传值(props与data，methods等同级)</li><li>子向父组件传值<strong>自定义事件</strong>  ||  <strong>$children</strong>  ||  <strong>$refs</strong></li></ul><blockquote><ul><li>$on() 监听事件</li><li>$emit() 在他上面触发时间</li></ul></blockquote><p><strong>考虑到树结构的层次复杂性</strong></p><blockquote><ul><li><del>$dispatch() 派发事件，事件沿着父链冒泡</del>（VUE2.0已弃用）</li><li><del>$broadcast() 广播事件，事件向下传到给所有后代</del>（VUE2.0已弃用）</li></ul></blockquote>]]></content>
    
    
    <summary type="html">Vue简单入门</summary>
    
    
    
    <category term="VUE" scheme="https://newgr8player.top/categories/VUE/"/>
    
    
    <category term="VUE" scheme="https://newgr8player.top/tags/VUE/"/>
    
  </entry>
  
  <entry>
    <title>Vue环境配置</title>
    <link href="https://newgr8player.top/posts/Vue%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE/"/>
    <id>https://newgr8player.top/posts/Vue%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE/</id>
    <published>2018-03-26T02:25:48.000Z</published>
    <updated>2024-12-13T07:02:02.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="环境安装配置"><a href="#环境安装配置" class="headerlink" title="环境安装配置"></a>环境安装配置</h1><h2 id="安装NodeJs"><a href="#安装NodeJs" class="headerlink" title="安装NodeJs"></a>安装NodeJs</h2><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">sudo apt install nodejs-legacy</span><br><span class="line">sudo apt install npm</span><br></pre></td></tr></table></figure><h2 id="升级npm为最新版本"><a href="#升级npm为最新版本" class="headerlink" title="升级npm为最新版本"></a>升级npm为最新版本</h2><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 npm install npm@latest -g</span><br></pre></td></tr></table></figure><p><strong>此时通过npm -v可以发现npm版本号为最新版本xxx</strong></p><h2 id="安装用于安装nodejs的模块n"><a href="#安装用于安装nodejs的模块n" class="headerlink" title="安装用于安装nodejs的模块n"></a>安装用于安装nodejs的模块n</h2><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 npm install -g n</span><br></pre></td></tr></table></figure><h2 id="安装版本"><a href="#安装版本" class="headerlink" title="安装版本"></a>安装版本</h2><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></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">#</span><span class="language-bash">安装官方最新版本</span></span><br><span class="line">sudo n latest</span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash">安装官方稳定版本</span></span><br><span class="line">sudo n stable</span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash">安装官方最新LTS版本</span></span><br><span class="line">sudo n lts</span><br></pre></td></tr></table></figure><h2 id="更换国内npm镜像"><a href="#更换国内npm镜像" class="headerlink" title="更换国内npm镜像"></a>更换国内npm镜像</h2><p><em><strong>更换后npm将变成cnpm</strong></em></p><h2 id="安装webpack"><a href="#安装webpack" class="headerlink" title="安装webpack"></a>安装webpack</h2><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 npm install webpack -g</span><br></pre></td></tr></table></figure><h2 id="安装vue脚手架"><a href="#安装vue脚手架" class="headerlink" title="安装vue脚手架"></a>安装vue脚手架</h2><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">cnpm/npm install vue-cli -g</span><br></pre></td></tr></table></figure><h2 id="初始化项目"><a href="#初始化项目" class="headerlink" title="初始化项目"></a>初始化项目</h2><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">vue init webpack [项目名称]</span><br></pre></td></tr></table></figure><h2 id="运行项目"><a href="#运行项目" class="headerlink" title="运行项目"></a>运行项目</h2><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></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">#</span><span class="language-bash">安装项目依赖 （添加依赖或者删除时，须重新安装）</span></span><br><span class="line">npm install</span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash">运行开发环境</span></span><br><span class="line">npm run dev</span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash">运行运营环境</span></span><br><span class="line">npm run build</span><br></pre></td></tr></table></figure><blockquote><p>运行成功可能调用浏览器或者在浏览器中输入<strong><a href="http://localhost:8080/">http://localhost:8080</a></strong>访问项目。</p></blockquote><p><strong>效果图如下</strong><br><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/25/2a2d5920-c6eb-11e9-bc99-47b0880b5ecf.png" alt="VUE配置成功后浏览器截图.png"></p>]]></content>
    
    
    <summary type="html">Vue环境配置</summary>
    
    
    
    <category term="VUE" scheme="https://newgr8player.top/categories/VUE/"/>
    
    
    <category term="VUE" scheme="https://newgr8player.top/tags/VUE/"/>
    
  </entry>
  
  <entry>
    <title>Python集成环境Anaconda使用</title>
    <link href="https://newgr8player.top/posts/Python%E9%9B%86%E6%88%90%E7%8E%AF%E5%A2%83Anaconda%E4%BD%BF%E7%94%A8/"/>
    <id>https://newgr8player.top/posts/Python%E9%9B%86%E6%88%90%E7%8E%AF%E5%A2%83Anaconda%E4%BD%BF%E7%94%A8/</id>
    <published>2018-03-24T08:00:28.000Z</published>
    <updated>2024-12-13T07:02:02.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="1-安装"><a href="#1-安装" class="headerlink" title="1. 安装"></a>1. 安装</h1><p><strong>下载地址</strong><br><a href="https://mirrors.tuna.tsinghua.edu.cn/">Anaconda镜像链接-清华大学开源镜像站</a></p><h1 id="2-windows下环境变量配置"><a href="#2-windows下环境变量配置" class="headerlink" title="2.windows下环境变量配置"></a>2.windows下环境变量配置</h1><p><em>默认添加到系统环境变量里面的路径是<strong>D:\Python\anaconda3</strong>（</em>此处为我安装的根目录<em>），建议更改为如下形式，方便目录转移。</em></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></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">#</span><span class="language-bash">设置CONDA_HOME</span></span><br><span class="line"><span class="meta prompt_">%</span><span class="language-bash">CONDA_HOME%</span></span><br><span class="line">D:\Python\anaconda3</span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash">环境中添加如下路径</span></span><br><span class="line"><span class="meta prompt_">%</span><span class="language-bash">CONDA_HOME%;</span></span><br><span class="line"><span class="meta prompt_">%</span><span class="language-bash">CONDA_HOME%\Scripts;</span></span><br></pre></td></tr></table></figure><h1 id="3-多环境并存"><a href="#3-多环境并存" class="headerlink" title="3. 多环境并存"></a>3. 多环境并存</h1><h2 id="3-1-查看当前存在版本"><a href="#3-1-查看当前存在版本" class="headerlink" title="3.1. 查看当前存在版本"></a>3.1. 查看当前存在版本</h2><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">conda info --envs</span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash">或者</span></span><br><span class="line">conda info -e</span><br></pre></td></tr></table></figure><p><strong>显示如下图</strong><br><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/25/1e383dc0-c6ea-11e9-ad5a-c9a6da72bdb3.png" alt="Anaconda在win下效果.png"></p><p><em><strong>显示当前存在的python环境，带有星号的 表示是当前活动的环境。可以发现环境的名称是以envs目录下文件夹名字命名的</strong></em></p><h2 id="3-2-创建环境"><a href="#3-2-创建环境" class="headerlink" title="3.2. 创建环境"></a>3.2. 创建环境</h2><p>通过<strong>anaconda-navigator</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">conda create --name 环境别名 python=环境版本</span><br></pre></td></tr></table></figure><h2 id="3-3-删除环境命令"><a href="#3-3-删除环境命令" class="headerlink" title="3.3. 删除环境命令"></a>3.3. 删除环境命令</h2><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">conda remove --name 环境别名 --all</span><br></pre></td></tr></table></figure><h2 id="3-4-切换版本"><a href="#3-4-切换版本" class="headerlink" title="3.4. 切换版本"></a>3.4. 切换版本</h2><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></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">#</span><span class="language-bash">Linux、Max OS下需要在前面加<span class="built_in">source</span></span></span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash">激活版本</span></span><br><span class="line">activate 版本别名</span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash">取消激活，退回默认版本</span></span><br><span class="line">deactivate 版本别名</span><br></pre></td></tr></table></figure><h1 id="4-修改编码"><a href="#4-修改编码" class="headerlink" title="4. 修改编码"></a>4. 修改编码</h1><p>python的默认编码是”ASCII“，修改为utf-8的方法：在Anaconda\Lib\site-packages目录下添加一个名字为sitecustomize.py文件，文件内容</p><figure class="highlight python"><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="keyword">import</span> sys  </span><br><span class="line">sys.setdefaultencoding(<span class="string">&#x27;utf-8&#x27;</span>)</span><br></pre></td></tr></table></figure><h1 id="5-设置国内源"><a href="#5-设置国内源" class="headerlink" title="5. 设置国内源"></a>5. 设置国内源</h1><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">conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/  </span><br><span class="line">conda config --set show_channel_urls yes </span><br></pre></td></tr></table></figure><h1 id="6-安装其他组件"><a href="#6-安装其他组件" class="headerlink" title="6. 安装其他组件"></a>6. 安装其他组件</h1><h2 id="6-1-安装spyder"><a href="#6-1-安装spyder" class="headerlink" title="6.1. 安装spyder"></a>6.1. 安装spyder</h2><p><em><strong>先切换python版本然后在相应版本下操作。</strong></em></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">conda install spyder</span><br></pre></td></tr></table></figure><h2 id="6-2-安装spyder"><a href="#6-2-安装spyder" class="headerlink" title="6.2. 安装spyder"></a>6.2. 安装spyder</h2><p><em><strong>先切换python版本然后在相应版本下操作。</strong></em></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">conda install jupyter</span><br></pre></td></tr></table></figure><h1 id="7-其他常用命令"><a href="#7-其他常用命令" class="headerlink" title="7. 其他常用命令"></a>7. 其他常用命令</h1><h2 id="7-1-包管理"><a href="#7-1-包管理" class="headerlink" title="7.1. 包管理"></a>7.1. 包管理</h2><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></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">安装 matplotlib</span> </span><br><span class="line">conda install matplotlib</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">查看已安装的包</span></span><br><span class="line">conda list </span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">包更新</span></span><br><span class="line">conda update matplotlib</span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">删除包</span></span><br><span class="line">conda remove matplotlib</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">Python集成环境Anaconda使用</summary>
    
    
    
    <category term="python" scheme="https://newgr8player.top/categories/python/"/>
    
    
    <category term="python" scheme="https://newgr8player.top/tags/python/"/>
    
  </entry>
  
  <entry>
    <title>设计模式-工厂模式</title>
    <link href="https://newgr8player.top/posts/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-%E5%B7%A5%E5%8E%82%E6%A8%A1%E5%BC%8F/"/>
    <id>https://newgr8player.top/posts/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-%E5%B7%A5%E5%8E%82%E6%A8%A1%E5%BC%8F/</id>
    <published>2018-03-24T04:46:04.000Z</published>
    <updated>2024-12-13T07:02:02.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="简单工厂模式"><a href="#简单工厂模式" class="headerlink" title="简单工厂模式"></a>简单工厂模式</h1><h2 id="使用简单工厂模式的优势"><a href="#使用简单工厂模式的优势" class="headerlink" title="使用简单工厂模式的优势"></a>使用简单工厂模式的优势</h2><blockquote><p>让对象的调用者和对象创建过程分离，当对象调用者需要对象时，直接向工厂请求即可。从而避免了对象的调用者与对象的实现类以硬编码方式耦合，以提高系统的可维护性，可扩展性。工厂模式也有一点缺陷：当产品修改时，工厂类也要做相应的修改。</p></blockquote><h2 id="下面实例"><a href="#下面实例" class="headerlink" title="下面实例"></a>下面实例</h2><p><strong>小张开车去东北</strong></p><blockquote><p>这里涉及到小张是乘坐什么交通工具去东北？汽车，飞机，火车…..都可以</p></blockquote><p><strong>Car类</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Car</span> <span class="keyword">implements</span> <span class="title class_">Moveable</span>&#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;冒着烟奔跑中car.......&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>Plane类</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Plane</span> <span class="keyword">implements</span> <span class="title class_">Moveable</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;扇着翅膀前进中plane....&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><strong>接口类Moveable</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">Moveable</span> &#123;</span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>抽象类VehicleFactory</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title class_">VehicleFactory</span> &#123;</span><br><span class="line">    <span class="keyword">abstract</span> Moveable <span class="title function_">create</span><span class="params">()</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>CarFactory类</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CarFactory</span> <span class="keyword">extends</span> <span class="title class_">VehicleFactory</span>&#123;</span><br><span class="line">    <span class="keyword">public</span> Moveable <span class="title function_">create</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Car</span>();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>PlaneFactory类</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">PlaneFactory</span> <span class="keyword">extends</span> <span class="title class_">VehicleFactory</span>&#123;</span><br><span class="line">    <span class="keyword">public</span> Moveable <span class="title function_">create</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Plane</span>();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>测试类</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Test</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line"></span><br><span class="line">        <span class="type">VehicleFactory</span> <span class="variable">factory</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">CarFactory</span>();</span><br><span class="line">        <span class="type">Moveable</span> <span class="variable">m</span> <span class="operator">=</span> factory.create();</span><br><span class="line"></span><br><span class="line">        m.run();</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><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/25/56faba70-c6ea-11e9-ad5a-c9a6da72bdb3.png" alt="类图之间的关系.png"></p><blockquote><p>此时如要换一种方式去东北，只需要按照上图给2个接口都添加实现即可。<br>由此我们可以想象，如果工厂中要产生大量的产品系列，那样的话，就需要各种添加大量的实现，容易造成工厂泛滥。</p></blockquote><h1 id="抽象工厂模式"><a href="#抽象工厂模式" class="headerlink" title="抽象工厂模式"></a>抽象工厂模式</h1><h2 id="定义及原因"><a href="#定义及原因" class="headerlink" title="定义及原因"></a>定义及原因</h2><blockquote><p>因为使用简单工厂模式，使得客户端代码成功地与被调用对象的实现类分离，但却带来了另外一种耦合：客户端代码与不同的工厂类耦合，这依然是一个问题。<br>我们考虑在增加一个工厂类，该类不生产具体的Vehicle类，而是生产VehicleFactory实例——简而言之,该类不制造具体的被调用对象，而是制造不同的工厂对象。这就是抽象工厂模式。</p></blockquote><h2 id="如下实例"><a href="#如下实例" class="headerlink" title="如下实例"></a>如下实例</h2><p><strong>抽象工厂类：制造不同的工厂对象</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title class_">AbstractFactory</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">abstract</span> Vehicle <span class="title function_">createVehicle</span><span class="params">()</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">abstract</span> Weapon <span class="title function_">createWeapon</span><span class="params">()</span>;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">abstract</span> Food <span class="title function_">createFood</span><span class="params">()</span>;</span><br><span class="line">    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>MagicFactory类：创建系列与魔法有关的食物，交通工具，武器对象</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MagicFactory</span> <span class="keyword">extends</span> <span class="title class_">AbstractFactory</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Food <span class="title function_">createFood</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">MushRoom</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> Vehicle <span class="title function_">createVehicle</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Broom</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> Weapon <span class="title function_">createWeapon</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">MagicStick</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><strong>DefaultFactory类：创建默认类型的食物，交通工具，武器对象</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DefaultFactory</span> <span class="keyword">extends</span> <span class="title class_">AbstractFactory</span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Food <span class="title function_">createFood</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Apple</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> Vehicle <span class="title function_">createVehicle</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Car</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> Weapon <span class="title function_">createWeapon</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">AK47</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><strong>Food类：生产Food</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title class_">Food</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">void</span> <span class="title function_">printName</span><span class="params">()</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>继承Food的两个类</strong></p><p><strong>Apple类</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Apple</span> <span class="keyword">extends</span> <span class="title class_">Food</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">printName</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;apple&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>MushRoom类</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MushRoom</span> <span class="keyword">extends</span> <span class="title class_">Food</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">printName</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;mushroom&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><strong>Vehicle类</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title class_">Vehicle</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>继承Vehicle类的两个类</strong></p><p><strong>Car类</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Car</span> <span class="keyword">extends</span> <span class="title class_">Vehicle</span> &#123;</span><br><span class="line">        </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;冒着烟奔跑中car.......&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>Broom类</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Broom</span>  <span class="keyword">extends</span> <span class="title class_">Vehicle</span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;一路沙尘暴飞奔而来broom.....&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><strong>Weapon类</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title class_">Weapon</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">void</span> <span class="title function_">shoot</span><span class="params">()</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>继承Weapon类的两个类</strong></p><p><strong>AK47类</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AK47</span> <span class="keyword">extends</span> <span class="title class_">Weapon</span>&#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">shoot</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;哒哒哒...&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>MagicStick类</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MagicStick</span> <span class="keyword">extends</span> <span class="title class_">Weapon</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">shoot</span><span class="params">()</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;fire hu hu hu ...&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><h2 id="下面是类图关系"><a href="#下面是类图关系" class="headerlink" title="下面是类图关系"></a>下面是类图关系</h2><p><img src="https://newgr8player-blog.oss-cn-beijing.aliyuncs.com/hexo-client/2019/08/25/6a0dffa0-c6ea-11e9-ad5a-c9a6da72bdb3.png" alt="类图关系.png"></p><blockquote><p>可以用配置文件配置，具体用法原来已经讲过；<br>冒着烟奔跑中car…….<br>哒哒哒…<br>apple<br>在抽象工厂模式中，如要产生大量产品品种对象，容易泛滥。</p></blockquote>]]></content>
    
    
    <summary type="html">设计模式-工厂模式</summary>
    
    
    
    <category term="设计模式" scheme="https://newgr8player.top/categories/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
    
    
    <category term="设计模式" scheme="https://newgr8player.top/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
    
  </entry>
  
  <entry>
    <title>设计模式-观察者模式</title>
    <link href="https://newgr8player.top/posts/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-%E8%A7%82%E5%AF%9F%E8%80%85%E6%A8%A1%E5%BC%8F/"/>
    <id>https://newgr8player.top/posts/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-%E8%A7%82%E5%AF%9F%E8%80%85%E6%A8%A1%E5%BC%8F/</id>
    <published>2018-03-24T03:51:44.000Z</published>
    <updated>2024-12-13T07:02:02.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><blockquote><p>观察者模式定义了对象间的一对多依赖关系，让一个或者多个观察者对象观察一个主题对象。当主题对象的状态发生变化时，系统能通知所有的依赖于此对象的观察者对象，从而使得观察者对象能自动更新。<br>在观察者模式中，被观察的对象通常被称为主题(Subject),依赖的对象被称为观察者(Observer)。在java中其实就有经典的AWT，比如按钮单击监听等等。<br>请模拟下面的情形：</p><ul><li>小孩在睡觉</li><li>醒来后要吃东西</li></ul></blockquote><h1 id="版本1"><a href="#版本1" class="headerlink" title="版本1"></a>版本1</h1><blockquote><p>主要思想：设计两个类Child和Dad，都是线程类，其中Dad类主动监测小孩是否还在睡觉，如果小孩一旦醒来，就喂它吃东西。</p></blockquote><p><strong>Child类:设置小孩的状态为睡着的，过了5秒，就醒来</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Child</span> <span class="keyword">implements</span> <span class="title class_">Runnable</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="type">boolean</span> <span class="variable">wakenUp</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br><span class="line">        </span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">wakeUp</span><span class="params">()</span>&#123;</span><br><span class="line">        wakenUp = <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                Thread.sleep(<span class="number">5000</span>);</span><br><span class="line">            &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">                e.printStackTrace();</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            wakeUp();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isWakenUp</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> wakenUp;</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_">setWakenUp</span><span class="params">(<span class="type">boolean</span> wakenUp)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.wakenUp = wakenUp;</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><strong>Dad类：将Child的引用传给Dad，监听小孩一旦醒来，就喂他吃的</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Dad</span> <span class="keyword">implements</span> <span class="title class_">Runnable</span>&#123;</span><br><span class="line"></span><br><span class="line">    Child c;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">Dad</span><span class="params">(Child c)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.c = c;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">feed</span><span class="params">(Child c)</span> &#123;</span><br><span class="line">       System.out.println(<span class="string">&quot;Child is feeded!&quot;</span>);    </span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">while</span>(!c.isWakenUp())&#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                Thread.sleep(<span class="number">5000</span>);</span><br><span class="line">            &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">                e.printStackTrace();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">            c.wakeUp();</span><br><span class="line">            feed(c);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>监听测试</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</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_">DadOberverChild</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">Child</span> <span class="variable">c</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Child</span>();</span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">Thread</span>(c).start();</span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">Thread</span>(<span class="keyword">new</span> <span class="title class_">Dad</span>(c)).start();</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>该设计有些不合理，主动地监测，爸爸要不停地看着小孩，内存消耗严重，浪费时间。</p></blockquote><h1 id="版本2"><a href="#版本2" class="headerlink" title="版本2"></a>版本2</h1><blockquote><p>修正版本1中cpu浪费的情形，现在不拿Dad来监控Child，反过来被动监测，让Child来监控Dad，比如你可以一边看欧洲杯，当儿子一醒来后，他就会用绳子拉着你去喂他。</p></blockquote><p><strong>Child类</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Child</span> <span class="keyword">implements</span> <span class="title class_">Runnable</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="type">boolean</span> <span class="variable">wakenUp</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> Dad d;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">Child</span><span class="params">(Dad d)</span>&#123;</span><br><span class="line">        <span class="built_in">this</span>.d = d;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">wakeUp</span><span class="params">()</span>&#123;</span><br><span class="line">        wakenUp = <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                Thread.sleep(<span class="number">5000</span>);</span><br><span class="line">            &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">                e.printStackTrace();</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            wakeUp();</span><br><span class="line">            d.feed(<span class="built_in">this</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">isWakenUp</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> wakenUp;</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_">setWakenUp</span><span class="params">(<span class="type">boolean</span> wakenUp)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.wakenUp = wakenUp;</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><strong>Dad类</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Dad</span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">feed</span><span class="params">(Child c)</span> &#123;</span><br><span class="line">       System.out.println(<span class="string">&quot;Child is feeded!&quot;</span>);    </span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>监听测试类</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DadOberverChild</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">Dad</span> <span class="variable">d</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Dad</span>();</span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">Thread</span>(<span class="keyword">new</span> <span class="title class_">Child</span>(d)).start();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1 id="版本3"><a href="#版本3" class="headerlink" title="版本3"></a>版本3</h1><blockquote><p>小孩一醒过来，触发一件事，就让爸爸对这件事做出反应，不管是喂他吃东西，还是抱他出去玩，都会使程序更灵活。</p></blockquote><p><strong>Child类</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Child</span> <span class="keyword">implements</span> <span class="title class_">Runnable</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> Dad d;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">Child</span><span class="params">(Dad d)</span>&#123;</span><br><span class="line">        <span class="built_in">this</span>.d = d;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">wakeUp</span><span class="params">()</span>&#123;</span><br><span class="line">        <span class="comment">//处理小孩从床上醒来的情况</span></span><br><span class="line">        d.ActionToWakenUp(<span class="keyword">new</span> <span class="title class_">WakenUpEvent</span>(System.currentTimeMillis(),<span class="string">&quot;bed&quot;</span>,<span class="built_in">this</span>));</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                Thread.sleep(<span class="number">5000</span>);</span><br><span class="line">            &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">                e.printStackTrace();</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">        <span class="built_in">this</span>.wakeUp();</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><strong>Dad类</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Dad</span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">feed</span><span class="params">(Child c)</span> &#123;</span><br><span class="line">       System.out.println(<span class="string">&quot;Child is feeded!&quot;</span>);    </span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//处理可能发生的事件</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">ActionToWakenUp</span><span class="params">(WakenUpEvent wakenUpEvent)</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;OK!&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>事件类：封装发生的事件对象</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</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_">WakenUpEvent</span> &#123;</span><br><span class="line">   <span class="keyword">private</span> <span class="type">long</span> wakenTime;    <span class="comment">//醒来时间</span></span><br><span class="line">   <span class="keyword">private</span> String location;   <span class="comment">//醒来地点</span></span><br><span class="line">   <span class="keyword">private</span> Child source;   <span class="comment">//事件源对象</span></span><br><span class="line">   </span><br><span class="line">   <span class="keyword">public</span> <span class="title function_">WakenUpEvent</span><span class="params">(<span class="type">long</span> wakenTime, String location, Child source)</span> </span><br><span class="line">   ......</span><br><span class="line">   <span class="keyword">public</span> <span class="title function_">Setter</span><span class="params">()</span>/Getter()</span><br><span class="line">   ......</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1 id="版本4"><a href="#版本4" class="headerlink" title="版本4"></a>版本4</h1><blockquote><p>对于小孩来说，一旦某件事发生，监听着这件事的人可能不只有一个，比如爸爸喂他吃东西，爷爷带他出去玩，奶奶给他开电视机.</p></blockquote><p><strong>Child类</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Child</span> <span class="keyword">implements</span> <span class="title class_">Runnable</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">//存放监听者对象</span></span><br><span class="line">    <span class="keyword">private</span> List&lt;WakenUpListener&gt; wakenUpListeners = <span class="keyword">new</span> <span class="title class_">ArrayList</span>&lt;WakenUpListener&gt;();</span><br><span class="line">    </span><br><span class="line">    <span class="comment">//为监听者对象添加监听器</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">addWakenUpListener</span><span class="params">(WakenUpListener l)</span>&#123;</span><br><span class="line">        wakenUpListeners.add(l);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">wakeUp</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">0</span>;i&lt;wakenUpListeners.size();i++)&#123;</span><br><span class="line">            <span class="type">WakenUpListener</span> <span class="variable">l</span> <span class="operator">=</span> wakenUpListeners.get(i);</span><br><span class="line">            l.ActionToWakenUp(<span class="keyword">new</span> <span class="title class_">WakenUpEvent</span>(System.currentTimeMillis(),<span class="string">&quot;bed&quot;</span>,<span class="built_in">this</span>));</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">//....</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>Dad类</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Dad</span> <span class="keyword">implements</span> <span class="title class_">WakenUpListener</span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">ActionToWakenUp</span><span class="params">(WakenUpEvent wakenUpEvent)</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;Feed Child!&quot;</span>);</span><br><span class="line">        </span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>GrendFather类</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">GrendFather</span> <span class="keyword">implements</span> <span class="title class_">WakenUpListener</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">ActionToWakenUp</span><span class="params">(WakenUpEvent wakenUpEvent)</span> &#123;</span><br><span class="line">        System.out.println(<span class="string">&quot;Hug Child!&quot;</span>);</span><br><span class="line">        </span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>监听者接口</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">WakenUpListener</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">ActionToWakenUp</span><span class="params">(WakenUpEvent wakenUpEvent)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>事件类之前就已经封转好了，不用改变。</p></blockquote><p><strong>监听测试类</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DadOberverChild</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">Child</span> <span class="variable">c</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Child</span>();</span><br><span class="line">        <span class="type">Dad</span> <span class="variable">d</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Dad</span>();</span><br><span class="line">        <span class="type">GrendFather</span> <span class="variable">gf</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">GrendFather</span>();</span><br><span class="line">        c.addWakenUpListener(d);</span><br><span class="line">        c.addWakenUpListener(gf);</span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">Thread</span>(c).start();</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> <strong>测试结果</strong></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">Feed Child!</span><br><span class="line">Hug Child!</span><br></pre></td></tr></table></figure><blockquote><p>程序改装到现在，灵活性非常高了，此时如果还有什么人要对小孩做什么事的话，只要再创建一个类就好，而且我们可以将要调用的类的信息放到配置文件中，在测试时，只需要读取配置文件中的信息就好，程序的可维护性变高了。</p></blockquote><blockquote><p>如：在程序中建立Observer.properties文件，随意添加对象名称。如：</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">observers = Dad,GrendFather</span><br></pre></td></tr></table></figure></blockquote><p><strong>监听测试类：解析资源文件Observer.properties</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DadOberverChild</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">//只加载一次</span></span><br><span class="line">    <span class="keyword">static</span> <span class="type">Properties</span>  <span class="variable">props</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Properties</span>();</span><br><span class="line">    <span class="keyword">static</span>&#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">//加载资源文件</span></span><br><span class="line">            props.load(DadOberverChild.class.getClassLoader().getResourceAsStream(<span class="string">&quot;Observer.properties&quot;</span>));</span><br><span class="line">        &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="title function_">DadOberverChild</span><span class="params">()</span>&#123;&#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">         <span class="type">Child</span> <span class="variable">c</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Child</span>();</span><br><span class="line">        <span class="comment">//取得文件中的内容并分解</span></span><br><span class="line">        String[] observers = props.getProperty(<span class="string">&quot;observers&quot;</span>).split(<span class="string">&quot;,&quot;</span>);</span><br><span class="line">        <span class="keyword">for</span>(String s : observers)&#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="comment">//创建对象，并添加监听者</span></span><br><span class="line">                c.addWakenUpListener((WakenUpListener)(Class.forName(s).newInstance()));</span><br><span class="line">            &#125; <span class="keyword">catch</span> (InstantiationException e) &#123;</span><br><span class="line">                e.printStackTrace();</span><br><span class="line">            &#125; <span class="keyword">catch</span> (IllegalAccessException e) &#123;</span><br><span class="line">                e.printStackTrace();</span><br><span class="line">            &#125; <span class="keyword">catch</span> (ClassNotFoundException e) &#123;</span><br><span class="line">                e.printStackTrace();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">Thread</span>(c).start();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">设计模式-观察者模式</summary>
    
    
    
    <category term="设计模式" scheme="https://newgr8player.top/categories/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
    
    
    <category term="设计模式" scheme="https://newgr8player.top/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
    
  </entry>
  
  <entry>
    <title>设计模式-单例模式</title>
    <link href="https://newgr8player.top/posts/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F/"/>
    <id>https://newgr8player.top/posts/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F/</id>
    <published>2018-03-24T03:40:57.000Z</published>
    <updated>2024-12-13T07:02:02.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="意图"><a href="#意图" class="headerlink" title="意图"></a>意图</h1><blockquote><p>保证一个类仅有一个实例，并提供一个访问它的全局访问点。<br>我们怎么样才能保证一个类只有一个实例并且这个实例易于被访问呢？<br>如果将对象赋值给一个java静态变量，那么你必须在程序一开始就创建好对象。万一这个对象非常耗费资源，&gt;而程序在这次的执行过程中又一直没有使用到它，不就形成浪费吗？<br>一个更好的办法是，让类自身负责保存它的唯一实例。这个类可以保证没有其他实例可以被创建（通过截取创建新对象的请求） ，并且它可以提供一个访问该实例的方法。这就是S i n g l e t o n模式，我们可以在需要时才创建对象。<br>在计算机系统中，线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。</p></blockquote><h1 id="单例模式的实现"><a href="#单例模式的实现" class="headerlink" title="单例模式的实现"></a>单例模式的实现</h1><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><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">LazyNotSecurtySingleton</span> &#123;</span><br><span class="line">    <span class="comment">//使用静态变量来记录Singleton类的唯一实例</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> LazyNotSecurtySingleton instance;</span><br><span class="line">    <span class="comment">// 私有构造，确保只有自类内部才能访问</span></span><br><span class="line">    <span class="keyword">private</span> <span class="title function_">LazyNotSecurtySingleton</span><span class="params">()</span> &#123;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">//返回该类的实例， 有线程同步问题    </span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span>  LazyNotSecurtySingleton <span class="title function_">getInstance</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (instance == <span class="literal">null</span>) &#123;</span><br><span class="line">            instance = <span class="keyword">new</span> <span class="title class_">LazyNotSecurtySingleton</span>();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> instance;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>Singleton通过将构造方法限定为private避免了类在外部被实例化，在同一个虚拟机范围内，Singleton的唯一实例只能通过getInstance()方法访问。（事实上，通过Java反射机制是能够实例化构造方法为private的类的，那基本上会使所有的Java单例实现失效。此问题在此处不做讨论，姑且掩耳盗铃地认为反射机制不存在。）<br>但是以上实现没有考虑线程安全问题。所谓线程安全是指：如果你的代码所在的进程中有多个线程在同时运行，而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的，而且其他的变量的值也和预期的是一样的，就是线程安全的。或者说：一个类或者程序所提供的接口对于线程来说是原子操作或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题。显然以上实现并不满足线程安全的要求，在并发环境下很可能出现多个Singleton实例。</p></blockquote><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><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="keyword">public</span> <span class="keyword">class</span> <span class="title class_">LazySecurtySingleton</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> LazySecurtySingleton instance;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 私有构造子，确保无法在类外实例化该类</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> <span class="title function_">LazySecurtySingleton</span><span class="params">()</span> &#123;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * synchronized关键字解决多个线程的同步问题</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">synchronized</span> LazySecurtySingleton <span class="title function_">getInstance</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (instance == <span class="literal">null</span>) &#123;</span><br><span class="line">            instance = <span class="keyword">new</span> <span class="title class_">LazySecurtySingleton</span>();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> instance;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>静态工厂方法中synchronized关键字提供的同步是必须的，否则当多个线程同时访问该方法时，无法确保获得的总是同一个实例。然而我们也看到，在所有的代码路径中，虽然只有第一次引用的时候需要对instance变量进行实例化，但是synchronized同步机制要求所有的代码执行路径都必须先获取类锁。在并发访问比较低时，效果并不显著，但是当并发访问量上升时，这里有可能会成为并发访问的瓶颈。</p></blockquote><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><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">EagerSingleton</span> &#123;  </span><br><span class="line">    <span class="comment">//私有的类成员常量  </span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">EagerSingleton</span> <span class="variable">SINGLETON</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">EagerSingleton</span>();  </span><br><span class="line">    <span class="comment">//私有的默认构造方法，此类不能被继承  </span></span><br><span class="line">    <span class="keyword">private</span> <span class="title function_">EagerSingleton</span><span class="params">()</span>&#123;&#125;  </span><br><span class="line">    <span class="comment">//静态工厂方法  </span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> EagerSingleton <span class="title function_">getInstance</span><span class="params">()</span>&#123;  </span><br><span class="line">        <span class="keyword">return</span> SINGLETON;  </span><br><span class="line">    &#125;  </span><br><span class="line">  </span><br><span class="line">&#125;  </span><br></pre></td></tr></table></figure><blockquote><p>这种方式，我们依赖JVM在加载这个类时马上创建该类的唯一实例，避免了线程安全问题。不过，instance在类装载时就实例化，没有达到lazy loading的效果.</p></blockquote><h2 id="第四种：双重校验锁"><a href="#第四种：双重校验锁" class="headerlink" title="第四种：双重校验锁"></a>第四种：双重校验锁</h2><blockquote><p>可以使用“双重检查加锁”的方式来实现，就可以既实现线程安全，又能够使性能不受很大的影响。那么什么是“双重检查加锁”机制呢？<br>所谓“双重检查加锁”机制，指的是：并不是每次进入getInstance方法都需要同步，而是先不同步，进入方法后，先检查实例是否存在，如果不存在才进行下面的同步块，这是第一重检查，<br>进入同步块过后，再次检查实例是否存在，如果不存在，就在同步的情况下创建一个实例，这是第二重检查。这样一来，就只需要同步一次了，从而减少了多次在同步情况下进行判断所浪费的时间。<br>“双重检查加锁”机制的实现会使用关键字volatile，它的意思是：被volatile修饰的变量的值，将不会被本地线程缓存，所有对该变量的读写都是直接操作共享内存，从而确保多个线程能正确的处理该变量。<br><strong>注意：在java1.4及以前版本中，很多JVM对于volatile关键字的实现的问题，会导致“双重检查加锁”的失败，因此“双重检查加锁”机制只能用在java5及以上的版本。</strong></p></blockquote><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="keyword">public</span> <span class="keyword">class</span> <span class="title class_">TwoLockSingleton</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">volatile</span> <span class="keyword">static</span> TwoLockSingleton singleton;</span><br><span class="line">    <span class="keyword">private</span> <span class="title function_">TwoLockSingleton</span><span class="params">()</span> &#123;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> TwoLockSingleton <span class="title function_">getInstance</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">//先检查实例是否存在，如果不存在才进入下面的同步块</span></span><br><span class="line">        <span class="keyword">if</span> (singleton == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="comment">//同步块，线程安全的创建实例</span></span><br><span class="line">            <span class="keyword">synchronized</span> (TwoLockSingleton.class) &#123;</span><br><span class="line">                <span class="comment">//再次检查实例是否存在，如果不存在才真正的创建实例</span></span><br><span class="line">                <span class="keyword">if</span> (singleton == <span class="literal">null</span>) &#123;</span><br><span class="line">                    singleton = <span class="keyword">new</span> <span class="title class_">TwoLockSingleton</span>();</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> singleton;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>这种实现方式既可以实现线程安全地创建实例，而又不会对性能造成太大的影响。它只是第一次创建实例的时候同步，以后就不需要同步了，从而加快了运行速度。<br><strong>提示：由于volatile关键字可能会屏蔽掉虚拟机中一些必要的代码优化，所以运行效率并不是很高。</strong><br>因此一般建议，没有特别的需要，不要使用。也就是说，虽然可以使用“双重检查加锁”机制来实现线程安全的单例，但并不建议大量采用，可以根据情况来选用。<br>根据上面的分析，常见的两种单例实现方式都存在小小的缺陷，那么有没有一种方案，既能实现延迟加载，又能实现线程安全呢？</p></blockquote><h2 id="第五种：静态内部类"><a href="#第五种：静态内部类" class="headerlink" title="第五种：静态内部类"></a>第五种：静态内部类</h2><blockquote><p>这个模式综合使用了Java的类级内部类和多线程缺省同步锁的知识，很巧妙地同时实现了延迟加载和线程安全。</p></blockquote><p><strong>1. 相应的基础知识</strong></p><blockquote><p><em><strong>什么是类级内部类</strong></em></p><ul><li>简单点说，类级内部类指的是，有static修饰的成员式内部类。如果没有static修饰的成员式内部类被称为对象级内部类。</li><li>类级内部类相当于其外部类的static成分，它的对象与外部类对象间不存在依赖关系，因此可直接创建。而对象级内部类的实例，是绑定在外部对象实例中的。</li><li>类级内部类中，可以定义静态的方法。在静态方法中只能够引用外部类中的静态成员方法或者成员变量。</li><li>类级内部类相当于其外部类的成员，只有在第一次被使用的时候才被会装载。</li></ul><p><em><strong>多线程缺省同步锁的知识</strong></em><br> 大家都知道，在多线程开发中，为了解决并发问题，主要是通过使用synchronized来加互斥锁进行同步控制。但是在某些情况中，JVM已经隐含地为您执行了同步，这些情况下就不用自己再来进行同步控制了。<br>这些情况包括：</p><ul><li>由静态初始化器（在静态字段上或static{}块中的初始化器）初始化数据时</li><li>访问final字段时</li><li>在创建线程之前创建对象时</li><li>线程可以看见它将要处理的对象时</li></ul></blockquote><p><strong>2. 解决方案的思路</strong></p><blockquote><p>要想很简单地实现线程安全，可以采用静态初始化器的方式，它可以由JVM来保证线程的安全性。比如前面的饿汉式实现方式。但是这样一来，不是会浪费一定的空间吗？因为这种实现方式，会在类装载的时候就初始化对象，不管你需不需要。<br>如果现在有一种方法能够让类装载的时候不去初始化对象，那不就解决问题了？一种可行的方式就是采用类级内部类，在这个类级内部类里面去创建对象实例。这样一来，只要不使用到这个类级内部类，那就不会创建对象实例，从而同时实现延迟加载和线程安全。</p></blockquote><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Singleton类被装载了，instance不一定被初始化。因为SingletonHolder类没有被主动使用，</span></span><br><span class="line"><span class="comment"> * 只有显示通过调用getInstance方法时，才会显示装载SingletonHolder类，从而实例化instance。</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">StaticInternalSingleton</span> &#123;</span><br><span class="line">    <span class="comment">/** </span></span><br><span class="line"><span class="comment">     * 类级的内部类，也就是静态的成员式内部类，该内部类的实例与外部类的实例没有绑定关系</span></span><br><span class="line"><span class="comment">     * 而且只有被调用到时才会装载，从而实现了延迟加载。</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">SingletonHolder</span> &#123;</span><br><span class="line">        <span class="comment">/**</span></span><br><span class="line"><span class="comment">         * 静态初始化器，由JVM来保证线程安全</span></span><br><span class="line"><span class="comment">         */</span></span><br><span class="line">        <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">static</span> <span class="type">StaticInternalSingleton</span> <span class="variable">INSTANCE</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StaticInternalSingleton</span>();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">private</span> <span class="title function_">StaticInternalSingleton</span><span class="params">()</span> &#123;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> StaticInternalSingleton <span class="title function_">getInstance</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> SingletonHolder.INSTANCE;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>JVM将推迟SingletonHolder 类的初始化,直到当getInstance方法第一次被调用的时候，它第一次读取SingletonHolder.instance，导致SingletonHolder类得到初始化；而这个类在装载并被初始化的时候，会初始化它的静态域，从而创建Singleton的实例，由于是静态的域，因此只会在虚拟机装载类的时候初始化一次，并由虚拟机来保证它的线程安全性。<br>这个模式的优势在于，getInstance方法并没有被同步，并且只是执行一个域的访问，因此延迟初始化并没有增加任何访问成本</p></blockquote><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><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 避免多线程同步问题，而且还能防止反序列化重新创建新的对象</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">enum</span> <span class="title class_">EnumSingleton</span> &#123;</span><br><span class="line">    INSTANCE;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">whateverMethod</span><span class="params">()</span> &#123;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>这种方式是Effective Java作者Josh Bloch 提倡的方式，它不仅能避免多线程同步问题，而且还能防止反序列化重新创建新的对象，使用枚举来实现单实例控制会更加简洁，而且无偿地提供了序列化机制，并由JVM从根本上提供保障，绝对防止多次实例化，是更简洁、高效、安全的实现单例的方式可以被继承的单例类，登记式单例类是为了克服饿汉式单例类和懒汉式单例类不可继承的缺点而设计的。</p></blockquote><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.util.HashMap;  </span><br><span class="line">  </span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RegSingleton</span> &#123;  </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> HashMap registry=<span class="keyword">new</span> <span class="title class_">HashMap</span>();  </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 静态代码块 </span></span><br><span class="line"><span class="comment">     * 静态代码块优先于主方法执行，而在类中定义的静态代码会优先于构造块执行，而且不管产生多少对象，静态代码块只执行一次。 </span></span><br><span class="line"><span class="comment">     */</span>  </span><br><span class="line">    <span class="keyword">static</span>&#123;  </span><br><span class="line">        RegSingleton singleton=<span class="keyword">new</span> <span class="title class_">RegSingleton</span>();  </span><br><span class="line">        registry.put(singleton.getClass().getName(), singleton);  </span><br><span class="line">    &#125;  </span><br><span class="line">      </span><br><span class="line">    <span class="keyword">protected</span> <span class="title function_">RegSingleton</span><span class="params">()</span>&#123;&#125;  </span><br><span class="line">      </span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> RegSingleton <span class="title function_">getInstance</span><span class="params">(String name)</span>&#123;  </span><br><span class="line">        <span class="keyword">if</span>(name==<span class="literal">null</span>)&#123;  </span><br><span class="line">            name=<span class="string">&quot;com.zzs.singleton.RegSingleton&quot;</span>;  </span><br><span class="line">        &#125;  </span><br><span class="line">        <span class="keyword">if</span>(registry.get(name)==<span class="literal">null</span>)&#123;  </span><br><span class="line">            <span class="keyword">try</span> &#123;  </span><br><span class="line">                registry.put(name, Class.forName(name).newInstance());  </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">        <span class="keyword">return</span> (RegSingleton) registry.get(name);  </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><strong>java代码</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RegSingletonChild</span> <span class="keyword">extends</span> <span class="title class_">RegSingleton</span> &#123;  </span><br><span class="line">    <span class="comment">//由于子类必须允许父类以构造方法调用产生实例，所以它的构造方法必须是公开的，protected或public  </span></span><br><span class="line">    <span class="keyword">protected</span> <span class="title function_">RegSingletonChild</span><span class="params">()</span> &#123;  </span><br><span class="line">    &#125;  </span><br><span class="line">    <span class="comment">//静态方法工厂  </span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> RegSingletonChild <span class="title function_">getInstance</span><span class="params">()</span> &#123;  </span><br><span class="line">        <span class="keyword">return</span> (RegSingletonChild) RegSingleton  </span><br><span class="line">                .getInstance(<span class="string">&quot;com.zzs.singleton.RegSingletonChild&quot;</span>);  </span><br><span class="line">    &#125;  </span><br><span class="line">&#125;  </span><br></pre></td></tr></table></figure><h1 id="Spring-singleton"><a href="#Spring-singleton" class="headerlink" title="Spring singleton"></a>Spring singleton</h1><blockquote><p>Spring容器最初提供了两种bean的scope类型：singleton和prototype，但发布2.0之后，又引入了另外三种scope类型，即request，session和global session类型。不过这三种类型有所限制，只能在web应用中使用，也就是说，只有在支持web应用的ApplicationContext中使用这三个scope才是合理的</p></blockquote><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">beans---org.springframework.beans.factory.support.AbstractBeanFactory/doGetBean ---singleton---prototype</span><br><span class="line">web---org.springframework.web.context.request.ServletRequestAttributes---request---session---global session</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">设计模式-单例模式</summary>
    
    
    
    <category term="设计模式" scheme="https://newgr8player.top/categories/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
    
    
    <category term="设计模式" scheme="https://newgr8player.top/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"/>
    
  </entry>
  
  <entry>
    <title>Centos上整合Apache + PHP + Tomcat7 + MySQL</title>
    <link href="https://newgr8player.top/posts/Centos%E4%B8%8A%E6%95%B4%E5%90%88Apache-PHP-Tomcat7-MySQL/"/>
    <id>https://newgr8player.top/posts/Centos%E4%B8%8A%E6%95%B4%E5%90%88Apache-PHP-Tomcat7-MySQL/</id>
    <published>2018-03-23T03:40:59.000Z</published>
    <updated>2024-12-13T07:02:02.000Z</updated>
    
    <content type="html"><![CDATA[<h1 id="安装Apahce-PHP-MySQL以及php连接mysql库的组件"><a href="#安装Apahce-PHP-MySQL以及php连接mysql库的组件" class="headerlink" title="安装Apahce, PHP, MySQL以及php连接mysql库的组件"></a>安装Apahce, PHP, MySQL以及php连接mysql库的组件</h1><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">yum install httpd php mysql mysql-server php-mysql</span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash">按照需要安装，我的服务器上已经有了tomcat 和 mysql ，所有就执行如下语句</span></span><br><span class="line">yum install httpd php php-mysql</span><br></pre></td></tr></table></figure><h1 id="安装apache扩展"><a href="#安装apache扩展" class="headerlink" title="安装apache扩展"></a>安装apache扩展</h1><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">yum install httpd-manual mod_ssl mod_perl mod_auth_mysql</span><br></pre></td></tr></table></figure><h1 id="安装php的常用扩展"><a href="#安装php的常用扩展" class="headerlink" title="安装php的常用扩展"></a>安装php的常用扩展</h1><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">yum install php-gd php-xml php-mbstring php-ldap php-pear php-xmlrpc</span><br></pre></td></tr></table></figure><h1 id="安装MySQL的扩展"><a href="#安装MySQL的扩展" class="headerlink" title="安装MySQL的扩展"></a>安装MySQL的扩展</h1><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">yum install mysql-connector-odbc mysql-devel libdbi-dbd-mysql</span><br></pre></td></tr></table></figure><h1 id="配置开机启动服务"><a href="#配置开机启动服务" class="headerlink" title="配置开机启动服务"></a>配置开机启动服务</h1><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">/sbin/chkconfig httpd on [设置apache httpd服务开机启动]</span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash">按需要，我一般自己设置脚本，开机运行脚本去启动程序</span></span><br></pre></td></tr></table></figure><h1 id="安装Tomcat7"><a href="#安装Tomcat7" class="headerlink" title="安装Tomcat7"></a>安装Tomcat7</h1><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">yum install java-1.6.0-openjdk java-1.6.0-openjdk-devel</span><br><span class="line">wget http://mirrors.hust.edu.cn/apache/tomcat/tomcat-7/v7.0.42/bin/apache-tomcat-7.0.42.tar.gz</span><br><span class="line">tar -zxvf apache-tomcat-7.0.42.tar.gz</span><br><span class="line">mv apache-tomcat-7.0.42 /usr/local/tomcat7</span><br></pre></td></tr></table></figure><h1 id="启动Tomcat7"><a href="#启动Tomcat7" class="headerlink" title="启动Tomcat7"></a>启动Tomcat7</h1><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">./usr/local/tomcat7/bin/startup.sh</span><br></pre></td></tr></table></figure><h1 id="测试tomcat"><a href="#测试tomcat" class="headerlink" title="测试tomcat"></a>测试tomcat</h1><blockquote><p>在浏览器地址栏输入http:&#x2F;&#x2F;你的IP:8080&#x2F;，可以看到Apache </p></blockquote><p>Tomcat的起始页，如果看不到，请确认是否是防火墙的问题。也可以修改tomcat的端口为80，此处不多说。</p><h1 id="Apache与Tomcat整合"><a href="#Apache与Tomcat整合" class="headerlink" title="Apache与Tomcat整合"></a>Apache与Tomcat整合</h1><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></pre></td><td class="code"><pre><span class="line"><span class="meta prompt_">#</span><span class="language-bash">这里我们使用简单的Proxy方式整合Apache与Tomcat</span></span><br><span class="line">vi /etc/httpd/conf.d/proxy_ajp.conf</span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash">添加 localhost 是你本机的IP 注意不要写错，如：12.34.56.78:8009</span></span><br><span class="line">ProxyPass /转发地址 ajp://localhost:8009/php项目名</span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash">（已有此文件的只需将相应内容前的注释符#删除即可）</span></span><br><span class="line"><span class="meta prompt_">#</span><span class="language-bash">保存修改后，重启Apache</span></span><br><span class="line">service httpd restart</span><br></pre></td></tr></table></figure><h1 id="测试整合结果"><a href="#测试整合结果" class="headerlink" title="测试整合结果"></a>测试整合结果</h1><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">在浏览器地址栏输入http://你的IP/，如果看到的是Apache Tomcat的起始页，恭喜你，Apache和Tomcat的整合已经成功了！</span><br></pre></td></tr></table></figure><h1 id="关于无法绑定80端口"><a href="#关于无法绑定80端口" class="headerlink" title="关于无法绑定80端口"></a>关于无法绑定80端口</h1><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"><span class="meta prompt_">#</span><span class="language-bash">修改conf/httpd.conf 文件内的监听ip地址为：</span></span><br><span class="line">ServerName 0.0.0.0:80</span><br></pre></td></tr></table></figure><h1 id="附：以上安装的软件文件及配置的路径如下："><a href="#附：以上安装的软件文件及配置的路径如下：" class="headerlink" title="附：以上安装的软件文件及配置的路径如下："></a>附：以上安装的软件文件及配置的路径如下：</h1><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">apache的配置文件在/etc/httpd/conf下</span><br><span class="line">apache的modules放在/usr/lib/httpd下</span><br><span class="line">php的配置文件在/etc/php.d/下 和/etc/php.ini</span><br><span class="line">php的modules放在/usr/lib/php/modules下</span><br><span class="line">Tomcat7的安装目录位于/usr/local/tomcat7</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">Centos上整合Apache + PHP + Tomcat7 + MySQL</summary>
    
    
    
    <category term="Linux" scheme="https://newgr8player.top/categories/Linux/"/>
    
    
    <category term="Centos" scheme="https://newgr8player.top/tags/Centos/"/>
    
    <category term="Apache" scheme="https://newgr8player.top/tags/Apache/"/>
    
    <category term="PHP" scheme="https://newgr8player.top/tags/PHP/"/>
    
    <category term="Mysql" scheme="https://newgr8player.top/tags/Mysql/"/>
    
    <category term="Tomcat" scheme="https://newgr8player.top/tags/Tomcat/"/>
    
  </entry>
  
</feed>
