3 Star 1 Fork 4

SnailClimb / SnailClimb

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
search.xml 603.43 KB
AI 代码解读
一键复制 编辑 原始数据 按行查看 历史
SnailClimb 提交于 2020-01-22 13:41 . Site updated: 2020-01-22 13:41:50
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>Java 成神之路(面试进阶+知识点夯实)</title>
<link href="/2019/12/31/java/%E7%9F%A5%E8%AF%86%E6%98%9F%E7%90%83:JavaGuide%E8%AF%BB%E8%80%85%E5%9C%88/"/>
<url>/2019/12/31/java/%E7%9F%A5%E8%AF%86%E6%98%9F%E7%90%83:JavaGuide%E8%AF%BB%E8%80%85%E5%9C%88/</url>
<content type="html"><![CDATA[<p>下面的文章每个都是非常非常不错的,认真阅读下来一定都会有收获。文章涉及的范围还是挺大的,考虑到很多人的时间有限,所以 Guide 哥我贴心的将自己觉得必看的一些文章都加上了【必看】标示。</p><p><strong>如果没有学习路线可以查看的话,可以查看这篇文章:<a href="https://articles.zsxq.com/id_z4nvalhg42wu.html" target="_blank" rel="noopener">Java 学习路线加方法推荐修订版(附带学习资源推荐)</a>,非常非常不错,根据自己实际经历总结而来。</strong></p><p>加油!每天进步一点点!</p><h2 id="面试进阶指南系列"><a href="#面试进阶指南系列" class="headerlink" title="面试进阶指南系列"></a>面试进阶指南系列</h2><h3 id="面试必知"><a href="#面试必知" class="headerlink" title="面试必知"></a>面试必知</h3><p>一些求职者必须要知道的东西,不要觉得这些非技术性的就没多大帮助,里面的很多内容只要认真看了,相信都会有所收获。</p><ol><li><strong><a href="https://articles.zsxq.com/id_xjmcqk7jls5r.html" target="_blank" rel="noopener">【必看】马上就要面试了?有哪些需要我们注意的地方?</a></strong></li><li><a href="https://articles.zsxq.com/id_ili0enc8hjvw.html" target="_blank" rel="noopener">包装严重的IT行业,作为面试官,我是如何甄别应聘者的包装程度</a></li><li><a href="https://articles.zsxq.com/id_vsnrcmo3r2pw.html" target="_blank" rel="noopener">大部分程序员在面试前很关心的一些问题(偏应届生向)</a></li><li><strong><a href="https://articles.zsxq.com/id_q11n4s8ewekr.html" target="_blank" rel="noopener">【必看】Github上有哪些对面试/学习Java有帮助的仓库?</a></strong></li><li><a href="https://articles.zsxq.com/id_qv4fzqq31vk2.html" target="_blank" rel="noopener">拒绝踩坑!如何巧妙化解面试官:“你有什么问题问我吗?”这类问题。</a></li><li><a href="https://articles.zsxq.com/id_p337epqk4m57.html" target="_blank" rel="noopener">当代程序员常见的面试标准</a></li></ol><h3 id="大佬们的面试经历"><a href="#大佬们的面试经历" class="headerlink" title="大佬们的面试经历"></a>大佬们的面试经历</h3><p>古人云:“<strong>他山之石,可以攻玉</strong>” 。善于学习借鉴别人的面试的成功经验或者失败的教训,可以让自己少走许多弯路。</p><ol><li><a href="https://articles.zsxq.com/id_10ehodaer1pb.html" target="_blank" rel="noopener">GitHub 8w+ Star 大佬的秋招求职回忆</a></li><li><a href="https://articles.zsxq.com/id_66h4nuqlgbcf.html" target="_blank" rel="noopener">【秋招 校招面试经历】5面阿里,终获offer.</a></li></ol><h2 id="系统架构"><a href="#系统架构" class="headerlink" title="系统架构"></a>系统架构</h2><ol><li><a href="https://articles.zsxq.com/id_j0s485wjtlwz.html" target="_blank" rel="noopener">后端程序员也要懂的性能测试知识</a></li></ol><h3 id="高并发"><a href="#高并发" class="headerlink" title="高并发"></a>高并发</h3><h4 id="消息中间件"><a href="#消息中间件" class="headerlink" title="消息中间件"></a>消息中间件</h4><h4 id="缓存"><a href="#缓存" class="headerlink" title="缓存"></a>缓存</h4><ol><li><a href="https://articles.zsxq.com/id_piib5bzwxzzo.html" target="_blank" rel="noopener">Redis 常见问题总结</a></li><li><a href="https://articles.zsxq.com/id_1x5tvc5n5l45.html" target="_blank" rel="noopener">几种常见的 Redis 集群以及使用场景</a></li></ol><h4 id="分库分表"><a href="#分库分表" class="headerlink" title="分库分表"></a>分库分表</h4>]]></content>
<categories>
<category> 面试 </category>
</categories>
<tags>
<tag> 面试 </tag>
<tag> Java </tag>
</tags>
</entry>
<entry>
<title>硬核!Java 并发进阶常见面试题总结!</title>
<link href="/2019/12/09/java/java%E5%A4%9A%E7%BA%BF%E7%A8%8B/Java%20%E5%B9%B6%E5%8F%91%E8%BF%9B%E9%98%B6%E5%B8%B8%E8%A7%81%E9%9D%A2%E8%AF%95%E9%A2%98%E6%80%BB%E7%BB%93/"/>
<url>/2019/12/09/java/java%E5%A4%9A%E7%BA%BF%E7%A8%8B/Java%20%E5%B9%B6%E5%8F%91%E8%BF%9B%E9%98%B6%E5%B8%B8%E8%A7%81%E9%9D%A2%E8%AF%95%E9%A2%98%E6%80%BB%E7%BB%93/</url>
<content type="html"><![CDATA[<h1 id="Java-并发进阶常见面试题总结"><a href="#Java-并发进阶常见面试题总结" class="headerlink" title="Java 并发进阶常见面试题总结"></a>Java 并发进阶常见面试题总结</h1><h2 id="1-synchronized-关键字"><a href="#1-synchronized-关键字" class="headerlink" title="1. synchronized 关键字"></a>1. synchronized 关键字</h2><h3 id="1-1-说一说自己对于-synchronized-关键字的了解"><a href="#1-1-说一说自己对于-synchronized-关键字的了解" class="headerlink" title="1.1. 说一说自己对于 synchronized 关键字的了解"></a>1.1. 说一说自己对于 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="1-2-说说自己是怎么使用-synchronized-关键字,在项目中用到了吗"><a href="#1-2-说说自己是怎么使用-synchronized-关键字,在项目中用到了吗" class="headerlink" title="1.2. 说说自己是怎么使用 synchronized 关键字,在项目中用到了吗"></a>1.2. 说说自己是怎么使用 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><pre class=" language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Singleton</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">volatile</span> <span class="token keyword">static</span> Singleton uniqueInstance<span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token function">Singleton</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> Singleton <span class="token function">getUniqueInstance</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">//先判断对象是否已经实例过,没有实例化过才进入加锁代码</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>uniqueInstance <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">//类对象加锁</span> <span class="token keyword">synchronized</span> <span class="token punctuation">(</span>Singleton<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>uniqueInstance <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span> uniqueInstance <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Singleton</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> uniqueInstance<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">}</span></code></pre><p>另外,需要注意 uniqueInstance 采用 volatile 关键字修饰也是很有必要。</p><p>uniqueInstance 采用 volatile 关键字修饰也是很有必要的, uniqueInstance = 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><pre class=" language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">SynchronizedDemo</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">method</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">synchronized</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"synchronized 代码块"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">}</span></code></pre><p>通过 JDK 自带的 javap 命令查看 SynchronizedDemo 类的相关字节码信息:首先切换到类的对应目录执行 <code>javac SynchronizedDemo.java</code> 命令生成编译后的 .class 文件,然后执行<code>javap -c -s -v -l SynchronizedDemo.class</code>。</p><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/synchronized%E5%85%B3%E9%94%AE%E5%AD%97%E5%8E%9F%E7%90%86.png" alt="synchronized关键字原理"></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><pre class=" language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">SynchronizedDemo2</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">synchronized</span> <span class="token keyword">void</span> <span class="token function">method</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"synchronized 方法"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">}</span></code></pre><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/synchronized%E5%85%B3%E9%94%AE%E5%AD%97%E5%8E%9F%E7%90%862.png" alt="synchronized关键字原理"></p><p>synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是 ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法,JVM 通过该 ACC_SYNCHRONIZED 访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。</p><h3 id="1-4-说说-JDK1-6-之后的synchronized-关键字底层做了哪些优化,可以详细介绍一下这些优化吗"><a href="#1-4-说说-JDK1-6-之后的synchronized-关键字底层做了哪些优化,可以详细介绍一下这些优化吗" class="headerlink" title="1.4. 说说 JDK1.6 之后的synchronized 关键字底层做了哪些优化,可以详细介绍一下这些优化吗"></a>1.4. 说说 JDK1.6 之后的synchronized 关键字底层做了哪些优化,可以详细介绍一下这些优化吗</h3><p>JDK1.6 对锁的实现引入了大量的优化,如偏向锁、轻量级锁、自旋锁、适应性自旋锁、锁消除、锁粗化等技术来减少锁操作的开销。</p><p>锁主要存在四种状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,他们会随着竞争的激烈而逐渐升级。注意锁可以升级不可降级,这种策略是为了提高获得锁和释放锁的效率。</p><p>关于这几种优化的详细信息可以查看笔主的这篇文章:<a href="https://gitee.com/SnailClimb/JavaGuide/blob/master/docs/java/Multithread/synchronized.md" target="_blank" rel="noopener">https://gitee.com/SnailClimb/JavaGuide/blob/master/docs/java/Multithread/synchronized.md</a></p><h3 id="1-5-谈谈-synchronized和ReentrantLock-的区别"><a href="#1-5-谈谈-synchronized和ReentrantLock-的区别" class="headerlink" title="1.5. 谈谈 synchronized和ReentrantLock 的区别"></a>1.5. 谈谈 synchronized和ReentrantLock 的区别</h3><p><strong>① 两者都是可重入锁</strong></p><p>两者都是可重入锁。“可重入锁”概念是:自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。同一个线程每次获取锁,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁。</p><p><strong>② synchronized 依赖于 JVM 而 ReentrantLock 依赖于 API</strong></p><p>synchronized 是依赖于 JVM 实现的,前面我们也讲到了 虚拟机团队在 JDK1.6 为 synchronized 关键字进行了很多优化,但是这些优化都是在虚拟机层面实现的,并没有直接暴露给我们。ReentrantLock 是 JDK 层面实现的(也就是 API 层面,需要 lock() 和 unlock() 方法配合 try/finally 语句块来完成),所以我们可以通过查看它的源代码,来看它是如何实现的。</p><p><strong>③ ReentrantLock 比 synchronized 增加了一些高级功能</strong></p><p>相比synchronized,ReentrantLock增加了一些高级功能。主要来说主要有三点:<strong>①等待可中断;②可实现公平锁;③可实现选择性通知(锁可以绑定多个条件)</strong></p><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()/notifyAll()方法相结合可以实现等待/通知机制,ReentrantLock类当然也可以实现,但是需要借助于Condition接口与newCondition() 方法。Condition是JDK1.5之后才有的,它具有很好的灵活性,比如可以实现多路通知功能也就是在一个Lock对象中可以创建多个Condition实例(即对象监视器),<strong>线程对象可以注册在指定的Condition中,从而可以有选择性的进行线程通知,在调度线程上更加灵活。 在使用notify()/notifyAll()方法进行通知时,被通知的线程是由 JVM 选择的,用ReentrantLock类结合Condition实例可以实现“选择性通知”</strong> ,这个功能非常重要,而且是Condition接口默认提供的。而synchronized关键字就相当于整个Lock对象中只有一个Condition实例,所有的线程都注册在它一个身上。如果执行notifyAll()方法的话就会通知所有处于等待状态的线程这样会造成很大的效率问题,而Condition实例的signalAll()方法 只会唤醒注册在该Condition实例中的所有等待线程。</li></ul><p>如果你想使用上述功能,那么选择ReentrantLock是一个不错的选择。</p><p><strong>④ 性能已不是选择标准</strong></p><h2 id="2-volatile关键字"><a href="#2-volatile关键字" class="headerlink" title="2. volatile关键字"></a>2. volatile关键字</h2><h3 id="2-1-讲一下Java内存模型"><a href="#2-1-讲一下Java内存模型" class="headerlink" title="2.1. 讲一下Java内存模型"></a>2.1. 讲一下Java内存模型</h3><p>在 JDK1.2 之前,Java的内存模型实现总是从<strong>主存</strong>(即共享内存)读取变量,是不需要进行特别的注意的。而在当前的 Java 内存模型下,线程可以把变量保存<strong>本地内存</strong>(比如机器的寄存器)中,而不是直接在主存中进行读写。这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值的拷贝,造成<strong>数据的不一致</strong>。</p><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/%E6%95%B0%E6%8D%AE%E4%B8%8D%E4%B8%80%E8%87%B4.png" alt="数据不一致"></p><p>要解决这个问题,就需要把变量声明为<strong>volatile</strong>,这就指示 JVM,这个变量是不稳定的,每次使用它都到主存中进行读取。</p><p>说白了, <strong>volatile</strong> 关键字的主要作用就是保证变量的可见性然后还有一个作用是防止指令重排序。</p><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/volatile%E5%85%B3%E9%94%AE%E5%AD%97%E7%9A%84%E5%8F%AF%E8%A7%81%E6%80%A7.png" alt="volatile关键字的可见性"></p><h3 id="2-2-说说-synchronized-关键字和-volatile-关键字的区别"><a href="#2-2-说说-synchronized-关键字和-volatile-关键字的区别" class="headerlink" title="2.2. 说说 synchronized 关键字和 volatile 关键字的区别"></a>2.2. 说说 synchronized 关键字和 volatile 关键字的区别</h3><p> synchronized关键字和volatile关键字比较</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="3-ThreadLocal"><a href="#3-ThreadLocal" class="headerlink" title="3. ThreadLocal"></a>3. ThreadLocal</h2><h3 id="3-1-ThreadLocal简介"><a href="#3-1-ThreadLocal简介" class="headerlink" title="3.1. ThreadLocal简介"></a>3.1. 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="3-2-ThreadLocal示例"><a href="#3-2-ThreadLocal示例" class="headerlink" title="3.2. ThreadLocal示例"></a>3.2. ThreadLocal示例</h3><p>相信看了上面的解释,大家已经搞懂 ThreadLocal 类是个什么东西了。</p><pre class=" language-java"><code class="language-java"><span class="token keyword">import</span> java<span class="token punctuation">.</span>text<span class="token punctuation">.</span>SimpleDateFormat<span class="token punctuation">;</span><span class="token keyword">import</span> java<span class="token punctuation">.</span>util<span class="token punctuation">.</span>Random<span class="token punctuation">;</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ThreadLocalExample</span> <span class="token keyword">implements</span> <span class="token class-name">Runnable</span><span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// SimpleDateFormat 不是线程安全的,所以每个线程都要有自己独立的副本</span> <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> ThreadLocal<span class="token operator">&lt;</span>SimpleDateFormat<span class="token operator">></span> formatter <span class="token operator">=</span> ThreadLocal<span class="token punctuation">.</span><span class="token function">withInitial</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token keyword">new</span> <span class="token class-name">SimpleDateFormat</span><span class="token punctuation">(</span><span class="token string">"yyyyMMdd HHmm"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token keyword">throws</span> InterruptedException <span class="token punctuation">{</span> ThreadLocalExample obj <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ThreadLocalExample</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">for</span><span class="token punctuation">(</span><span class="token keyword">int</span> i<span class="token operator">=</span><span class="token number">0</span> <span class="token punctuation">;</span> i<span class="token operator">&lt;</span><span class="token number">10</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span><span class="token punctuation">{</span> Thread t <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span>obj<span class="token punctuation">,</span> <span class="token string">""</span><span class="token operator">+</span>i<span class="token punctuation">)</span><span class="token punctuation">;</span> Thread<span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Random</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">nextInt</span><span class="token punctuation">(</span><span class="token number">1000</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> t<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token annotation punctuation">@Override</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">run</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"Thread Name= "</span><span class="token operator">+</span>Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">+</span><span class="token string">" default Formatter = "</span><span class="token operator">+</span>formatter<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toPattern</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> Thread<span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Random</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">nextInt</span><span class="token punctuation">(</span><span class="token number">1000</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">InterruptedException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span> e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">//formatter pattern is changed here by thread, but it won't reflect to other threads</span> formatter<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">SimpleDateFormat</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"Thread Name= "</span><span class="token operator">+</span>Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">+</span><span class="token string">" formatter = "</span><span class="token operator">+</span>formatter<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toPattern</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">}</span></code></pre><p>Output:</p><pre><code>Thread Name= 0 default Formatter = yyyyMMdd HHmmThread Name= 0 formatter = yy-M-d ah:mmThread Name= 1 default Formatter = yyyyMMdd HHmmThread Name= 2 default Formatter = yyyyMMdd HHmmThread Name= 1 formatter = yy-M-d ah:mmThread Name= 3 default Formatter = yyyyMMdd HHmmThread Name= 2 formatter = yy-M-d ah:mmThread Name= 4 default Formatter = yyyyMMdd HHmmThread Name= 3 formatter = yy-M-d ah:mmThread Name= 4 formatter = yy-M-d ah:mmThread Name= 5 default Formatter = yyyyMMdd HHmmThread Name= 5 formatter = yy-M-d ah:mmThread Name= 6 default Formatter = yyyyMMdd HHmmThread Name= 6 formatter = yy-M-d ah:mmThread Name= 7 default Formatter = yyyyMMdd HHmmThread Name= 7 formatter = yy-M-d ah:mmThread Name= 8 default Formatter = yyyyMMdd HHmmThread Name= 9 default Formatter = yyyyMMdd HHmmThread Name= 8 formatter = yy-M-d ah:mmThread Name= 9 formatter = yy-M-d ah:mm</code></pre><p>从输出中可以看出,Thread-0已经改变了formatter的值,但仍然是thread-2默认格式化程序与初始化值相同,其他线程也一样。</p><p>上面有一段代码用到了创建 <code>ThreadLocal</code> 变量的那段代码用到了 Java8 的知识,它等于下面这段代码,如果你写了下面这段代码的话,IDEA会提示你转换为Java8的格式(IDEA真的不错!)。因为ThreadLocal类在Java 8中扩展,使用一个新的方法<code>withInitial()</code>,将Supplier功能接口作为参数。</p><pre class=" language-java"><code class="language-java"> <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> ThreadLocal<span class="token operator">&lt;</span>SimpleDateFormat<span class="token operator">></span> formatter <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ThreadLocal</span><span class="token operator">&lt;</span>SimpleDateFormat<span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span> <span class="token annotation punctuation">@Override</span> <span class="token keyword">protected</span> SimpleDateFormat <span class="token function">initialValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">SimpleDateFormat</span><span class="token punctuation">(</span><span class="token string">"yyyyMMdd HHmm"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre><h3 id="3-3-ThreadLocal原理"><a href="#3-3-ThreadLocal原理" class="headerlink" title="3.3. ThreadLocal原理"></a>3.3. ThreadLocal原理</h3><p>从 <code>Thread</code>类源代码入手。</p><pre class=" language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Thread</span> <span class="token keyword">implements</span> <span class="token class-name">Runnable</span> <span class="token punctuation">{</span> <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token comment" spellcheck="true">//与此线程有关的ThreadLocal值。由ThreadLocal类维护</span>ThreadLocal<span class="token punctuation">.</span>ThreadLocalMap threadLocals <span class="token operator">=</span> null<span class="token punctuation">;</span><span class="token comment" spellcheck="true">//与此线程有关的InheritableThreadLocal值。由InheritableThreadLocal类维护</span>ThreadLocal<span class="token punctuation">.</span>ThreadLocalMap inheritableThreadLocals <span class="token operator">=</span> null<span class="token punctuation">;</span> <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">}</span></code></pre><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><pre class=" language-java"><code class="language-java"> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">set</span><span class="token punctuation">(</span>T value<span class="token punctuation">)</span> <span class="token punctuation">{</span> Thread t <span class="token operator">=</span> Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> ThreadLocalMap map <span class="token operator">=</span> <span class="token function">getMap</span><span class="token punctuation">(</span>t<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>map <span class="token operator">!=</span> null<span class="token punctuation">)</span> map<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> value<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">else</span> <span class="token function">createMap</span><span class="token punctuation">(</span>t<span class="token punctuation">,</span> value<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> ThreadLocalMap <span class="token function">getMap</span><span class="token punctuation">(</span>Thread t<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> t<span class="token punctuation">.</span>threadLocals<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre><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://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/ThreadLocal%E5%86%85%E9%83%A8%E7%B1%BB.png" alt="ThreadLocal内部类"></p><h3 id="3-4-ThreadLocal-内存泄露问题"><a href="#3-4-ThreadLocal-内存泄露问题" class="headerlink" title="3.4. ThreadLocal 内存泄露问题"></a>3.4. 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><pre class=" language-java"><code class="language-java"> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">Entry</span> <span class="token keyword">extends</span> <span class="token class-name">WeakReference</span><span class="token operator">&lt;</span>ThreadLocal<span class="token operator">&lt;</span><span class="token operator">?</span><span class="token operator">>></span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">/** The value associated with this ThreadLocal. */</span> Object value<span class="token punctuation">;</span> <span class="token function">Entry</span><span class="token punctuation">(</span>ThreadLocal<span class="token operator">&lt;</span><span class="token operator">?</span><span class="token operator">></span> k<span class="token punctuation">,</span> Object v<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">super</span><span class="token punctuation">(</span>k<span class="token punctuation">)</span><span class="token punctuation">;</span> value <span class="token operator">=</span> v<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre><p><strong>弱引用介绍:</strong></p><blockquote><p>如果一个对象只具有弱引用,那就类似于<strong>可有可无的生活用品</strong>。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它 所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。</p><p>弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。</p></blockquote><h2 id="4-线程池"><a href="#4-线程池" class="headerlink" title="4. 线程池"></a>4. 线程池</h2><h3 id="4-1-为什么要用线程池?"><a href="#4-1-为什么要用线程池?" class="headerlink" title="4.1. 为什么要用线程池?"></a>4.1. 为什么要用线程池?</h3><blockquote><p><strong>池化技术相比大家已经屡见不鲜了,线程池、数据库连接池、Http 连接池等等都是对这个思想的应用。池化技术的思想主要是为了减少每次获取资源的消耗,提高对资源的利用率。</strong></p></blockquote><p><strong>线程池</strong>提供了一种限制和管理资源(包括执行一个任务)。 每个<strong>线程池</strong>还维护一些基本统计信息,例如已完成任务的数量。</p><p>这里借用《Java 并发编程的艺术》提到的来说一下<strong>使用线程池的好处</strong>:</p><ul><li><strong>降低资源消耗</strong>。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。</li><li><strong>提高响应速度</strong>。当任务到达时,任务可以不需要的等到线程创建就能立即执行。</li><li><strong>提高线程的可管理性</strong>。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。</li></ul><h3 id="4-2-实现Runnable接口和Callable接口的区别"><a href="#4-2-实现Runnable接口和Callable接口的区别" class="headerlink" title="4.2. 实现Runnable接口和Callable接口的区别"></a>4.2. 实现Runnable接口和Callable接口的区别</h3><p><code>Runnable</code>自Java 1.0以来一直存在,但<code>Callable</code>仅在Java 1.5中引入,目的就是为了来处理<code>Runnable</code>不支持的用例。<strong><code>Runnable</code> 接口</strong>不会返回结果或抛出检查异常,但是<strong><code>Callable</code> 接口</strong>可以。所以,如果任务不需要返回结果或抛出异常推荐使用 <strong><code>Runnable</code> 接口</strong>,这样代码看起来会更加简洁。</p><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><p><code>Runnable.java</code></p><pre class=" language-java"><code class="language-java"><span class="token annotation punctuation">@FunctionalInterface</span><span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">Runnable</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">/** * 被线程执行,没有返回值也无法抛出异常 */</span> <span class="token keyword">public</span> <span class="token keyword">abstract</span> <span class="token keyword">void</span> <span class="token function">run</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span></code></pre><p><code>Callable.java</code></p><pre class=" language-java"><code class="language-java"><span class="token annotation punctuation">@FunctionalInterface</span><span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">Callable</span><span class="token operator">&lt;</span>V<span class="token operator">></span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">/** * 计算结果,或在无法这样做时抛出异常。 * @return 计算得出的结果 * @throws 如果无法计算结果,则抛出异常 */</span> V <span class="token function">call</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">throws</span> Exception<span class="token punctuation">;</span><span class="token punctuation">}</span></code></pre><h3 id="4-3-执行execute-方法和submit-方法的区别是什么呢?"><a href="#4-3-执行execute-方法和submit-方法的区别是什么呢?" class="headerlink" title="4.3. 执行execute()方法和submit()方法的区别是什么呢?"></a>4.3. 执行execute()方法和submit()方法的区别是什么呢?</h3><ol><li><strong><code>execute()</code>方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否;</strong></li><li><strong><code>submit()</code>方法用于提交需要返回值的任务。线程池会返回一个 <code>Future</code> 类型的对象,通过这个 <code>Future</code> 对象可以判断任务是否执行成功</strong>,并且可以通过 <code>Future</code> 的 <code>get()</code>方法来获取返回值,<code>get()</code>方法会阻塞当前线程直到任务完成,而使用 <code>get(long timeout,TimeUnit unit)</code>方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。</li></ol><p>我们以<strong><code>AbstractExecutorService</code></strong>接口中的一个 <code>submit</code> 方法为例子来看看源代码:</p><pre class=" language-java"><code class="language-java"> <span class="token keyword">public</span> Future<span class="token operator">&lt;</span><span class="token operator">?</span><span class="token operator">></span> <span class="token function">submit</span><span class="token punctuation">(</span>Runnable task<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>task <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">NullPointerException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> RunnableFuture<span class="token operator">&lt;</span>Void<span class="token operator">></span> ftask <span class="token operator">=</span> <span class="token function">newTaskFor</span><span class="token punctuation">(</span>task<span class="token punctuation">,</span> null<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">execute</span><span class="token punctuation">(</span>ftask<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> ftask<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre><p>上面方法调用的 <code>newTaskFor</code> 方法返回了一个 <code>FutureTask</code> 对象。</p><pre class=" language-java"><code class="language-java"> <span class="token keyword">protected</span> <span class="token operator">&lt;</span>T<span class="token operator">></span> RunnableFuture<span class="token operator">&lt;</span>T<span class="token operator">></span> <span class="token function">newTaskFor</span><span class="token punctuation">(</span>Runnable runnable<span class="token punctuation">,</span> T value<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">FutureTask</span><span class="token operator">&lt;</span>T<span class="token operator">></span><span class="token punctuation">(</span>runnable<span class="token punctuation">,</span> value<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre><p>我们再来看看<code>execute()</code>方法:</p><pre class=" language-java"><code class="language-java"> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">execute</span><span class="token punctuation">(</span>Runnable command<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span> <span class="token punctuation">}</span></code></pre><h3 id="4-4-如何创建线程池"><a href="#4-4-如何创建线程池" class="headerlink" title="4.4. 如何创建线程池"></a>4.4. 如何创建线程池</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://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/ThreadPoolExecutor%E6%9E%84%E9%80%A0%E6%96%B9%E6%B3%95.png" alt="ThreadPoolExecutor构造方法"><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://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/Executor%E6%A1%86%E6%9E%B6%E7%9A%84%E5%B7%A5%E5%85%B7%E7%B1%BB.png" alt="Executor框架的工具类"></p><h3 id="4-5-ThreadPoolExecutor-类分析"><a href="#4-5-ThreadPoolExecutor-类分析" class="headerlink" title="4.5 ThreadPoolExecutor 类分析"></a>4.5 ThreadPoolExecutor 类分析</h3><p><code>ThreadPoolExecutor</code> 类中提供的四个构造方法。我们来看最长的那个,其余三个都是在这个构造方法的基础上产生(其他几个构造方法说白点都是给定某些默认参数的构造方法比如默认制定拒绝策略是什么),这里就不贴代码讲了,比较简单。</p><pre class=" language-java"><code class="language-java"> <span class="token comment" spellcheck="true">/** * 用给定的初始参数创建一个新的ThreadPoolExecutor。 */</span> <span class="token keyword">public</span> <span class="token function">ThreadPoolExecutor</span><span class="token punctuation">(</span><span class="token keyword">int</span> corePoolSize<span class="token punctuation">,</span> <span class="token keyword">int</span> maximumPoolSize<span class="token punctuation">,</span> <span class="token keyword">long</span> keepAliveTime<span class="token punctuation">,</span> TimeUnit unit<span class="token punctuation">,</span> BlockingQueue<span class="token operator">&lt;</span>Runnable<span class="token operator">></span> workQueue<span class="token punctuation">,</span> ThreadFactory threadFactory<span class="token punctuation">,</span> RejectedExecutionHandler handler<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>corePoolSize <span class="token operator">&lt;</span> <span class="token number">0</span> <span class="token operator">||</span> maximumPoolSize <span class="token operator">&lt;=</span> <span class="token number">0</span> <span class="token operator">||</span> maximumPoolSize <span class="token operator">&lt;</span> corePoolSize <span class="token operator">||</span> keepAliveTime <span class="token operator">&lt;</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">IllegalArgumentException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>workQueue <span class="token operator">==</span> null <span class="token operator">||</span> threadFactory <span class="token operator">==</span> null <span class="token operator">||</span> handler <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">NullPointerException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>corePoolSize <span class="token operator">=</span> corePoolSize<span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>maximumPoolSize <span class="token operator">=</span> maximumPoolSize<span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>workQueue <span class="token operator">=</span> workQueue<span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>keepAliveTime <span class="token operator">=</span> unit<span class="token punctuation">.</span><span class="token function">toNanos</span><span class="token punctuation">(</span>keepAliveTime<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>threadFactory <span class="token operator">=</span> threadFactory<span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>handler <span class="token operator">=</span> handler<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre><p><strong>下面这些对创建 非常重要,在后面使用线程池的过程中你一定会用到!所以,务必拿着小本本记清楚。</strong></p><h4 id="4-5-1-ThreadPoolExecutor构造函数重要参数分析"><a href="#4-5-1-ThreadPoolExecutor构造函数重要参数分析" class="headerlink" title="4.5.1 ThreadPoolExecutor构造函数重要参数分析"></a>4.5.1 <code>ThreadPoolExecutor</code>构造函数重要参数分析</h4><p><strong><code>ThreadPoolExecutor</code> 3 个最重要的参数:</strong></p><ul><li><strong><code>corePoolSize</code> :</strong> 核心线程数线程数定义了最小可以同时运行的线程数量。</li><li><strong><code>maximumPoolSize</code> :</strong> 当队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数。</li><li><strong><code>workQueue</code>:</strong> 当新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,新任务就会被存放在队列中。</li></ul><p><code>ThreadPoolExecutor</code>其他常见参数:</p><ol><li><strong><code>keepAliveTime</code></strong>:当线程池中的线程数量大于 <code>corePoolSize</code> 的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了 <code>keepAliveTime</code>才会被回收销毁;</li><li><strong><code>unit</code></strong> : <code>keepAliveTime</code> 参数的时间单位。</li><li><strong><code>threadFactory</code></strong> :executor 创建新线程的时候会用到。</li><li><strong><code>handler</code></strong> :饱和策略。关于饱和策略下面单独介绍一下。</li></ol><h4 id="4-5-2-ThreadPoolExecutor-饱和策略"><a href="#4-5-2-ThreadPoolExecutor-饱和策略" class="headerlink" title="4.5.2 ThreadPoolExecutor 饱和策略"></a>4.5.2 <code>ThreadPoolExecutor</code> 饱和策略</h4><p><strong><code>ThreadPoolExecutor</code> 饱和策略定义:</strong></p><p>如果当前同时运行的线程数量达到最大线程数量并且队列也已经被放满了任时,<code>ThreadPoolTaskExecutor</code> 定义一些策略:</p><ul><li><strong><code>ThreadPoolExecutor.AbortPolicy</code></strong>:抛出 <code>RejectedExecutionException</code>来拒绝新任务的处理。</li><li><strong><code>ThreadPoolExecutor.CallerRunsPolicy</code></strong>:调用执行自己的线程运行任务。您不会任务请求。但是这种策略会降低对于新任务提交速度,影响程序的整体性能。另外,这个策略喜欢增加队列容量。如果您的应用程序可以承受此延迟并且你不能任务丢弃任何一个任务请求的话,你可以选择这个策略。</li><li><strong><code>ThreadPoolExecutor.DiscardPolicy</code>:</strong> 不处理新任务,直接丢弃掉。</li><li><strong><code>ThreadPoolExecutor.DiscardOldestPolicy</code>:</strong> 此策略将丢弃最早的未处理的任务请求。</li></ul><p>举个例子: Spring 通过 <code>ThreadPoolTaskExecutor</code> 或者我们直接通过 <code>ThreadPoolExecutor</code> 的构造函数创建线程池的时候,当我们不指定 <code>RejectedExecutionHandler</code> 饱和策略的话来配置线程池的时候默认使用的是 <code>ThreadPoolExecutor.AbortPolicy</code>。在默认情况下,<code>ThreadPoolExecutor</code> 将抛出 <code>RejectedExecutionException</code> 来拒绝新来的任务 ,这代表你将丢失对这个任务的处理。 对于可伸缩的应用程序,建议使用 <code>ThreadPoolExecutor.CallerRunsPolicy</code>。当最大池被填满时,此策略为我们提供可伸缩队列。(这个直接查看 <code>ThreadPoolExecutor</code> 的构造函数源码就可以看出,比较简单的原因,这里就不贴代码了)</p><h3 id="4-6-一个简单的线程池Demo-Runnable-ThreadPoolExecutor"><a href="#4-6-一个简单的线程池Demo-Runnable-ThreadPoolExecutor" class="headerlink" title="4.6 一个简单的线程池Demo:Runnable+ThreadPoolExecutor"></a>4.6 一个简单的线程池Demo:<code>Runnable</code>+<code>ThreadPoolExecutor</code></h3><p>为了让大家更清楚上面的面试题中的一些概念,我写了一个简单的线程池 Demo。</p><p>首先创建一个 <code>Runnable</code> 接口的实现类(当然也可以是 <code>Callable</code> 接口,我们上面也说了两者的区别。)</p><p><code>MyRunnable.java</code></p><pre class=" language-java"><code class="language-java"><span class="token keyword">import</span> java<span class="token punctuation">.</span>util<span class="token punctuation">.</span>Date<span class="token punctuation">;</span><span class="token comment" spellcheck="true">/** * 这是一个简单的Runnable类,需要大约5秒钟来执行其任务。 * @author shuang.kou */</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">MyRunnable</span> <span class="token keyword">implements</span> <span class="token class-name">Runnable</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> String command<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token function">MyRunnable</span><span class="token punctuation">(</span>String s<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>command <span class="token operator">=</span> s<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token annotation punctuation">@Override</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">run</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">" Start. Time = "</span> <span class="token operator">+</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">processCommand</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">" End. Time = "</span> <span class="token operator">+</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">processCommand</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> Thread<span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">5000</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">InterruptedException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span> e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token annotation punctuation">@Override</span> <span class="token keyword">public</span> String <span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span>command<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">}</span></code></pre><p>编写测试程序,我们这里以阿里巴巴推荐的使用 <code>ThreadPoolExecutor</code> 构造函数自定义参数的方式来创建线程池。</p><p><code>ThreadPoolExecutorDemo.java</code></p><pre class=" language-java"><code class="language-java"><span class="token keyword">import</span> java<span class="token punctuation">.</span>util<span class="token punctuation">.</span>concurrent<span class="token punctuation">.</span>ArrayBlockingQueue<span class="token punctuation">;</span><span class="token keyword">import</span> java<span class="token punctuation">.</span>util<span class="token punctuation">.</span>concurrent<span class="token punctuation">.</span>ThreadPoolExecutor<span class="token punctuation">;</span><span class="token keyword">import</span> java<span class="token punctuation">.</span>util<span class="token punctuation">.</span>concurrent<span class="token punctuation">.</span>TimeUnit<span class="token punctuation">;</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ThreadPoolExecutorDemo</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> CORE_POOL_SIZE <span class="token operator">=</span> <span class="token number">5</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> MAX_POOL_SIZE <span class="token operator">=</span> <span class="token number">10</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> QUEUE_CAPACITY <span class="token operator">=</span> <span class="token number">100</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> Long KEEP_ALIVE_TIME <span class="token operator">=</span> 1L<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">//使用阿里巴巴推荐的创建线程池的方式</span> <span class="token comment" spellcheck="true">//通过ThreadPoolExecutor构造函数自定义参数创建</span> ThreadPoolExecutor executor <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ThreadPoolExecutor</span><span class="token punctuation">(</span> CORE_POOL_SIZE<span class="token punctuation">,</span> MAX_POOL_SIZE<span class="token punctuation">,</span> KEEP_ALIVE_TIME<span class="token punctuation">,</span> TimeUnit<span class="token punctuation">.</span>SECONDS<span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name">ArrayBlockingQueue</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span>QUEUE_CAPACITY<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name">ThreadPoolExecutor<span class="token punctuation">.</span>CallerRunsPolicy</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> <span class="token number">10</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">//创建WorkerThread对象(WorkerThread类实现了Runnable 接口)</span> Runnable worker <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">MyRunnable</span><span class="token punctuation">(</span><span class="token string">""</span> <span class="token operator">+</span> i<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//执行Runnable</span> executor<span class="token punctuation">.</span><span class="token function">execute</span><span class="token punctuation">(</span>worker<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">//终止线程池</span> executor<span class="token punctuation">.</span><span class="token function">shutdown</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token operator">!</span>executor<span class="token punctuation">.</span><span class="token function">isTerminated</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"Finished all threads"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">}</span></code></pre><p>可以看到我们上面的代码指定了:</p><ol><li><code>corePoolSize</code>: 核心线程数为 5。</li><li><code>maximumPoolSize</code> :最大线程数 10</li><li><code>keepAliveTime</code> : 等待时间为 1L。</li><li><code>unit</code>: 等待时间的单位为 TimeUnit.SECONDS。</li><li><code>workQueue</code>:任务队列为 <code>ArrayBlockingQueue</code>,并且容量为 100;</li><li><code>handler</code>:饱和策略为 <code>CallerRunsPolicy</code>。</li></ol><p><strong>Output:</strong></p><pre><code>pool-1-thread-2 Start. Time = Tue Nov 12 20:59:44 CST 2019pool-1-thread-5 Start. Time = Tue Nov 12 20:59:44 CST 2019pool-1-thread-4 Start. Time = Tue Nov 12 20:59:44 CST 2019pool-1-thread-1 Start. Time = Tue Nov 12 20:59:44 CST 2019pool-1-thread-3 Start. Time = Tue Nov 12 20:59:44 CST 2019pool-1-thread-5 End. Time = Tue Nov 12 20:59:49 CST 2019pool-1-thread-3 End. Time = Tue Nov 12 20:59:49 CST 2019pool-1-thread-2 End. Time = Tue Nov 12 20:59:49 CST 2019pool-1-thread-4 End. Time = Tue Nov 12 20:59:49 CST 2019pool-1-thread-1 End. Time = Tue Nov 12 20:59:49 CST 2019pool-1-thread-2 Start. Time = Tue Nov 12 20:59:49 CST 2019pool-1-thread-1 Start. Time = Tue Nov 12 20:59:49 CST 2019pool-1-thread-4 Start. Time = Tue Nov 12 20:59:49 CST 2019pool-1-thread-3 Start. Time = Tue Nov 12 20:59:49 CST 2019pool-1-thread-5 Start. Time = Tue Nov 12 20:59:49 CST 2019pool-1-thread-2 End. Time = Tue Nov 12 20:59:54 CST 2019pool-1-thread-3 End. Time = Tue Nov 12 20:59:54 CST 2019pool-1-thread-4 End. Time = Tue Nov 12 20:59:54 CST 2019pool-1-thread-5 End. Time = Tue Nov 12 20:59:54 CST 2019pool-1-thread-1 End. Time = Tue Nov 12 20:59:54 CST 2019</code></pre><h3 id="4-7-线程池原理分析"><a href="#4-7-线程池原理分析" class="headerlink" title="4.7 线程池原理分析"></a>4.7 线程池原理分析</h3><p>承接 4.6 节,我们通过代码输出结果可以看出:<strong>线程池每次会同时执行 5 个任务,这 5 个任务执行完之后,剩余的 5 个任务才会被执行。</strong> 大家可以先通过上面讲解的内容,分析一下到底是咋回事?(自己独立思考一会)</p><p>现在,我们就分析上面的输出内容来简单分析一下线程池原理。</p><p><strong>为了搞懂线程池的原理,我们需要首先分析一下 <code>execute</code>方法。</strong>在 4.6 节中的 Demo 中我们使用 <code>executor.execute(worker)</code>来提交一个任务到线程池中去,这个方法非常重要,下面我们来看看它的源码:</p><pre class=" language-java"><code class="language-java"> <span class="token comment" spellcheck="true">// 存放线程池的运行状态 (runState) 和线程池内有效线程的数量 (workerCount)</span> <span class="token keyword">private</span> <span class="token keyword">final</span> AtomicInteger ctl <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">AtomicInteger</span><span class="token punctuation">(</span><span class="token function">ctlOf</span><span class="token punctuation">(</span>RUNNING<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">int</span> <span class="token function">workerCountOf</span><span class="token punctuation">(</span><span class="token keyword">int</span> c<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> c <span class="token operator">&amp;</span> CAPACITY<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">private</span> <span class="token keyword">final</span> BlockingQueue<span class="token operator">&lt;</span>Runnable<span class="token operator">></span> workQueue<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">execute</span><span class="token punctuation">(</span>Runnable command<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// 如果任务为null,则抛出异常。</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>command <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">NullPointerException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// ctl 中保存的线程池当前的一些状态信息</span> <span class="token keyword">int</span> c <span class="token operator">=</span> ctl<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 下面会涉及到 3 步 操作</span> <span class="token comment" spellcheck="true">// 1.首先判断当前线程池中之行的任务数量是否小于 corePoolSize</span> <span class="token comment" spellcheck="true">// 如果小于的话,通过addWorker(command, true)新建一个线程,并将任务(command)添加到该线程中;然后,启动该线程从而执行任务。</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">workerCountOf</span><span class="token punctuation">(</span>c<span class="token punctuation">)</span> <span class="token operator">&lt;</span> corePoolSize<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">addWorker</span><span class="token punctuation">(</span>command<span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span><span class="token punctuation">;</span> c <span class="token operator">=</span> ctl<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">// 2.如果当前之行的任务数量大于等于 corePoolSize 的时候就会走到这里</span> <span class="token comment" spellcheck="true">// 通过 isRunning 方法判断线程池状态,线程池处于 RUNNING 状态才会被并且队列可以加入任务,该任务才会被加入进去</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">isRunning</span><span class="token punctuation">(</span>c<span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> workQueue<span class="token punctuation">.</span><span class="token function">offer</span><span class="token punctuation">(</span>command<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">int</span> recheck <span class="token operator">=</span> ctl<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 再次获取线程池状态,如果线程池状态不是 RUNNING 状态就需要从任务队列中移除任务,并尝试判断线程是否全部执行完毕。同时执行拒绝策略。</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">isRunning</span><span class="token punctuation">(</span>recheck<span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> <span class="token function">remove</span><span class="token punctuation">(</span>command<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token function">reject</span><span class="token punctuation">(</span>command<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 如果当前线程池为空就新创建一个线程并执行。</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">workerCountOf</span><span class="token punctuation">(</span>recheck<span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token function">addWorker</span><span class="token punctuation">(</span>null<span class="token punctuation">,</span> <span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">//3. 通过addWorker(command, false)新建一个线程,并将任务(command)添加到该线程中;然后,启动该线程从而执行任务。</span> <span class="token comment" spellcheck="true">//如果addWorker(command, false)执行失败,则通过reject()执行相应的拒绝策略的内容。</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">addWorker</span><span class="token punctuation">(</span>command<span class="token punctuation">,</span> <span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token function">reject</span><span class="token punctuation">(</span>command<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre><p>通过下图可以更好的对上面这 3 步做一个展示,下图是我为了省事直接从网上找到,原地址不明。</p><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-7/%E5%9B%BE%E8%A7%A3%E7%BA%BF%E7%A8%8B%E6%B1%A0%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86.png" alt="图解线程池实现原理"></p><p>现在,让我们在回到 4.6 节我们写的 Demo, 现在应该是不是很容易就可以搞懂它的原理了呢?</p><p>没搞懂的话,也没关系,可以看看我的分析:</p><blockquote><p>我们在代码中模拟了 10 个任务,我们配置的核心线程数为 5 、等待队列容量为 100 ,所以每次只可能存在 5 个任务同时执行,剩下的 5 个任务会被放到等待队列中去。当前的 5 个任务之行完成后,才会之行剩下的 5 个任务。</p></blockquote><h2 id="5-Atomic-原子类"><a href="#5-Atomic-原子类" class="headerlink" title="5. Atomic 原子类"></a>5. Atomic 原子类</h2><h3 id="5-1-介绍一下Atomic-原子类"><a href="#5-1-介绍一下Atomic-原子类" class="headerlink" title="5.1. 介绍一下Atomic 原子类"></a>5.1. 介绍一下Atomic 原子类</h3><p>Atomic 翻译成中文是原子的意思。在化学上,我们知道原子是构成一般物质的最小单位,在化学反应中是不可分割的。在我们这里 Atomic 是指一个操作是不可中断的。即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰。</p><p>所以,所谓原子类说简单点就是具有原子/原子操作特征的类。</p><p>并发包 <code>java.util.concurrent</code> 的原子类都存放在<code>java.util.concurrent.atomic</code>下,如下图所示。</p><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/JUC%E5%8E%9F%E5%AD%90%E7%B1%BB%E6%A6%82%E8%A7%88.png" alt="JUC原子类概览"></p><h3 id="5-2-JUC-包中的原子类是哪4类"><a href="#5-2-JUC-包中的原子类是哪4类" class="headerlink" title="5.2. JUC 包中的原子类是哪4类?"></a>5.2. 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="5-3-讲讲-AtomicInteger-的使用"><a href="#5-3-讲讲-AtomicInteger-的使用" class="headerlink" title="5.3. 讲讲 AtomicInteger 的使用"></a>5.3. 讲讲 AtomicInteger 的使用</h3><p> <strong>AtomicInteger 类常用方法</strong></p><pre class=" language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token keyword">int</span> <span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">//获取当前的值</span><span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token keyword">int</span> <span class="token function">getAndSet</span><span class="token punctuation">(</span><span class="token keyword">int</span> newValue<span class="token punctuation">)</span><span class="token comment" spellcheck="true">//获取当前的值,并设置新的值</span><span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token keyword">int</span> <span class="token function">getAndIncrement</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true">//获取当前的值,并自增</span><span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token keyword">int</span> <span class="token function">getAndDecrement</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">//获取当前的值,并自减</span><span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token keyword">int</span> <span class="token function">getAndAdd</span><span class="token punctuation">(</span><span class="token keyword">int</span> delta<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">//获取当前的值,并加上预期的值</span><span class="token keyword">boolean</span> <span class="token function">compareAndSet</span><span class="token punctuation">(</span><span class="token keyword">int</span> expect<span class="token punctuation">,</span> <span class="token keyword">int</span> update<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">//如果输入的数值等于预期值,则以原子方式将该值设置为输入值(update)</span><span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token keyword">void</span> <span class="token function">lazySet</span><span class="token punctuation">(</span><span class="token keyword">int</span> newValue<span class="token punctuation">)</span><span class="token comment" spellcheck="true">//最终设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。</span></code></pre><p> <strong>AtomicInteger 类的使用示例</strong></p><p>使用 AtomicInteger 之后,不用对 increment() 方法加锁也可以保证线程安全。</p><pre class=" language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">AtomicIntegerTest</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> AtomicInteger count <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">AtomicInteger</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//使用AtomicInteger之后,不需要对该方法加锁,也可以实现线程安全。</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">increment</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> count<span class="token punctuation">.</span><span class="token function">incrementAndGet</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">getCount</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> count<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">}</span></code></pre><h3 id="5-4-能不能给我简单介绍一下-AtomicInteger-类的原理"><a href="#5-4-能不能给我简单介绍一下-AtomicInteger-类的原理" class="headerlink" title="5.4. 能不能给我简单介绍一下 AtomicInteger 类的原理"></a>5.4. 能不能给我简单介绍一下 AtomicInteger 类的原理</h3><p>AtomicInteger 线程安全原理简单分析</p><p>AtomicInteger 类的部分源码:</p><pre class=" language-java"><code class="language-java"> <span class="token comment" spellcheck="true">// setup to use Unsafe.compareAndSwapInt for updates(更新操作时提供“比较并替换”的作用)</span> <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> Unsafe unsafe <span class="token operator">=</span> Unsafe<span class="token punctuation">.</span><span class="token function">getUnsafe</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">long</span> valueOffset<span class="token punctuation">;</span> <span class="token keyword">static</span> <span class="token punctuation">{</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> valueOffset <span class="token operator">=</span> unsafe<span class="token punctuation">.</span><span class="token function">objectFieldOffset</span> <span class="token punctuation">(</span>AtomicInteger<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">.</span><span class="token function">getDeclaredField</span><span class="token punctuation">(</span><span class="token string">"value"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Exception</span> ex<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span>ex<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">private</span> <span class="token keyword">volatile</span> <span class="token keyword">int</span> value<span class="token punctuation">;</span></code></pre><p>AtomicInteger 类主要利用 CAS (compare and swap) + volatile 和 native 方法来保证原子操作,从而避免 synchronized 的高开销,执行效率大为提升。</p><p>CAS的原理是拿期望的值和原本的一个值作比较,如果相同则更新成新的值。UnSafe 类的 objectFieldOffset() 方法是一个本地方法,这个方法是用来拿到“原来的值”的内存地址,返回值是 valueOffset。另外 value 是一个volatile变量,在内存中可见,因此 JVM 可以保证任何时刻任何线程总能拿到该变量的最新值。</p><p>关于 Atomic 原子类这部分更多内容可以查看我的这篇文章:并发编程面试必备:<a href="https://mp.weixin.qq.com/s/joa-yOiTrYF67bElj8xqvg" target="_blank" rel="noopener">JUC 中的 Atomic 原子类总结</a></p><h2 id="6-AQS"><a href="#6-AQS" class="headerlink" title="6. AQS"></a>6. AQS</h2><h3 id="6-1-AQS-介绍"><a href="#6-1-AQS-介绍" class="headerlink" title="6.1. AQS 介绍"></a>6.1. AQS 介绍</h3><p>AQS的全称为(AbstractQueuedSynchronizer),这个类在java.util.concurrent.locks包下面。</p><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/AQS%E7%B1%BB.png" alt="AQS类"></p><p>AQS是一个用来构建锁和同步器的框架,使用AQS能简单且高效地构造出应用广泛的大量的同步器,比如我们提到的ReentrantLock,Semaphore,其他的诸如ReentrantReadWriteLock,SynchronousQueue,FutureTask等等皆是基于AQS的。当然,我们自己也能利用AQS非常轻松容易地构造出符合我们自己需求的同步器。</p><h3 id="6-2-AQS-原理分析"><a href="#6-2-AQS-原理分析" class="headerlink" title="6.2. AQS 原理分析"></a>6.2. AQS 原理分析</h3><p>AQS 原理这部分参考了部分博客,在5.2节末尾放了链接。</p><blockquote><p>在面试中被问到并发知识的时候,大多都会被问到“请你说一下自己对于AQS原理的理解”。下面给大家一个示例供大家参加,面试不是背题,大家一定要加入自己的思想,即使加入不了自己的思想也要保证自己能够通俗的讲出来而不是背出来。</p></blockquote><p>下面大部分内容其实在AQS类注释上已经给出了,不过是英语看着比较吃力一点,感兴趣的话可以看看源码。</p><h4 id="6-2-1-AQS-原理概览"><a href="#6-2-1-AQS-原理概览" class="headerlink" title="6.2.1. AQS 原理概览"></a>6.2.1. 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://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/AQS%E5%8E%9F%E7%90%86%E5%9B%BE.png" alt="AQS原理图"></p><p>AQS使用一个int成员变量来表示同步状态,通过内置的FIFO队列来完成获取资源线程的排队工作。AQS使用CAS对该同步状态进行原子操作实现对其值的修改。</p><pre class=" language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">volatile</span> <span class="token keyword">int</span> state<span class="token punctuation">;</span><span class="token comment" spellcheck="true">//共享变量,使用volatile修饰保证线程可见性</span></code></pre><p>状态信息通过protected类型的getState,setState,compareAndSetState进行操作</p><pre class=" language-java"><code class="language-java"><span class="token comment" spellcheck="true">//返回同步状态的当前值</span><span class="token keyword">protected</span> <span class="token keyword">final</span> <span class="token keyword">int</span> <span class="token function">getState</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> state<span class="token punctuation">;</span><span class="token punctuation">}</span> <span class="token comment" spellcheck="true">// 设置同步状态的值</span><span class="token keyword">protected</span> <span class="token keyword">final</span> <span class="token keyword">void</span> <span class="token function">setState</span><span class="token punctuation">(</span><span class="token keyword">int</span> newState<span class="token punctuation">)</span> <span class="token punctuation">{</span> state <span class="token operator">=</span> newState<span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token comment" spellcheck="true">//原子地(CAS操作)将同步状态值设置为给定值update如果当前同步状态的值等于expect(期望值)</span><span class="token keyword">protected</span> <span class="token keyword">final</span> <span class="token keyword">boolean</span> <span class="token function">compareAndSetState</span><span class="token punctuation">(</span><span class="token keyword">int</span> expect<span class="token punctuation">,</span> <span class="token keyword">int</span> update<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> unsafe<span class="token punctuation">.</span><span class="token function">compareAndSwapInt</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> stateOffset<span class="token punctuation">,</span> expect<span class="token punctuation">,</span> update<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span></code></pre><h4 id="6-2-2-AQS-对资源的共享方式"><a href="#6-2-2-AQS-对资源的共享方式" class="headerlink" title="6.2.2. AQS 对资源的共享方式"></a>6.2.2. 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/CountDownLatch。Semaphore、CountDownLatch、 CyclicBarrier、ReadWriteLock 我们都会在后面讲到。</li></ul><p>ReentrantReadWriteLock 可以看成是组合式,因为ReentrantReadWriteLock也就是读写锁允许多个线程同时对某一资源进行读。</p><p>不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源 state 的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在顶层实现好了。</p><h4 id="6-2-3-AQS底层使用了模板方法模式"><a href="#6-2-3-AQS底层使用了模板方法模式" class="headerlink" title="6.2.3. AQS底层使用了模板方法模式"></a>6.2.3. AQS底层使用了模板方法模式</h4><p>同步器的设计是基于模板方法模式的,如果需要自定义同步器一般的方式是这样(模板方法模式很经典的一个应用):</p><ol><li>使用者继承AbstractQueuedSynchronizer并重写指定的方法。(这些重写方法很简单,无非是对于共享资源state的获取和释放)</li><li>将AQS组合在自定义同步组件的实现中,并调用其模板方法,而这些模板方法会调用使用者重写的方法。</li></ol><p>这和我们以往通过实现接口的方式有很大区别,这是模板方法模式很经典的一个运用。</p><p><strong>AQS使用了模板方法模式,自定义同步器时需要重写下面几个AQS提供的模板方法:</strong></p><pre class=" language-java"><code class="language-java"><span class="token function">isHeldExclusively</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true">//该线程是否正在独占资源。只有用到condition才需要去实现它。</span><span class="token function">tryAcquire</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true">//独占方式。尝试获取资源,成功则返回true,失败则返回false。</span><span class="token function">tryRelease</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true">//独占方式。尝试释放资源,成功则返回true,失败则返回false。</span><span class="token function">tryAcquireShared</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true">//共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。</span><span class="token function">tryReleaseShared</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">)</span><span class="token comment" spellcheck="true">//共享方式。尝试释放资源,成功则返回true,失败则返回false。</span></code></pre><p>默认情况下,每个方法都抛出 <code>UnsupportedOperationException</code>。 这些方法的实现必须是内部线程安全的,并且通常应该简短而不是阻塞。AQS类中的其他方法都是final ,所以无法被其他类使用,只有这几个方法可以被其他类使用。 </p><p>以ReentrantLock为例,state初始化为0,表示未锁定状态。A线程lock()时,会调用tryAcquire()独占该锁并将state+1。此后,其他线程再tryAcquire()时就会失败,直到A线程unlock()到state=0(即释放锁)为止,其它线程才有机会获取该锁。当然,释放锁之前,A线程自己是可以重复获取此锁的(state会累加),这就是可重入的概念。但要注意,获取多少次就要释放多么次,这样才能保证state是能回到零态的。</p><p>再以CountDownLatch以例,任务分为N个子线程去执行,state也初始化为N(注意N要与线程个数一致)。这N个子线程是并行执行的,每个子线程执行完后countDown()一次,state会CAS(Compare and Swap)减1。等到所有子线程都执行完后(即state=0),会unpark()主调用线程,然后主调用线程就会从await()函数返回,继续后余动作。</p><p>一般来说,自定义同步器要么是独占方法,要么是共享方式,他们也只需实现<code>tryAcquire-tryRelease</code>、<code>tryAcquireShared-tryReleaseShared</code>中的一种即可。但AQS也支持自定义同步器同时实现独占和共享两种方式,如<code>ReentrantReadWriteLock</code>。</p><p>推荐两篇 AQS 原理和相关源码分析的文章:</p><ul><li><a href="http://www.cnblogs.com/waterystone/p/4920797.html" target="_blank" rel="noopener">http://www.cnblogs.com/waterystone/p/4920797.html</a></li><li><a href="https://www.cnblogs.com/chengxiao/archive/2017/07/24/7141160.html" target="_blank" rel="noopener">https://www.cnblogs.com/chengxiao/archive/2017/07/24/7141160.html</a></li></ul><h3 id="6-3-AQS-组件总结"><a href="#6-3-AQS-组件总结" class="headerlink" title="6.3. AQS 组件总结"></a>6.3. 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="7-Reference"><a href="#7-Reference" class="headerlink" title="7 Reference"></a>7 Reference</h2><ul><li>《深入理解 Java 虚拟机》</li><li>《实战 Java 高并发程序设计》</li><li>《Java并发编程的艺术》</li><li><a href="http://www.cnblogs.com/waterystone/p/4920797.html" target="_blank" rel="noopener">http://www.cnblogs.com/waterystone/p/4920797.html</a></li><li><a href="https://www.cnblogs.com/chengxiao/archive/2017/07/24/7141160.html" target="_blank" rel="noopener">https://www.cnblogs.com/chengxiao/archive/2017/07/24/7141160.html</a></li><li><a href="https://www.journaldev.com/1076/java-threadlocal-example" target="_blank" rel="noopener">https://www.journaldev.com/1076/java-threadlocal-example</a></li></ul><h2 id="公众号"><a href="#公众号" class="headerlink" title="公众号"></a>公众号</h2><p>如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。</p><p><strong>《Java面试突击》:</strong> 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本<a href="#公众号">公众号</a>后台回复 <strong>“面试突击”</strong> 即可免费领取!</p><p><strong>Java工程师必备学习资源:</strong> 一些Java工程师常用学习资源公众号后台回复关键字 <strong>“1”</strong> 即可免费无套路获取。 </p><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/167598cd2e17b8ec.png" alt="我的公众号"></p>]]></content>
<categories>
<category> Java多线程 </category>
</categories>
<tags>
<tag> Java </tag>
<tag> 多线程 </tag>
</tags>
</entry>
<entry>
<title>硬核!Java 并发基础常见面试题总结!</title>
<link href="/2019/12/09/java/java%E5%A4%9A%E7%BA%BF%E7%A8%8B/Java%20%E5%B9%B6%E5%8F%91%E5%9F%BA%E7%A1%80%E5%B8%B8%E8%A7%81%E9%9D%A2%E8%AF%95%E9%A2%98%E6%80%BB%E7%BB%93/"/>
<url>/2019/12/09/java/java%E5%A4%9A%E7%BA%BF%E7%A8%8B/Java%20%E5%B9%B6%E5%8F%91%E5%9F%BA%E7%A1%80%E5%B8%B8%E8%A7%81%E9%9D%A2%E8%AF%95%E9%A2%98%E6%80%BB%E7%BB%93/</url>
<content type="html"><![CDATA[<h2 id="1-什么是线程和进程"><a href="#1-什么是线程和进程" class="headerlink" title="1. 什么是线程和进程?"></a>1. 什么是线程和进程?</h2><h3 id="1-1-何为进程"><a href="#1-1-何为进程" class="headerlink" title="1.1. 何为进程?"></a>1.1. 何为进程?</h3><p>进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。</p><p>在 Java 中,当我们启动 main 函数时其实就是启动了一个 JVM 的进程,而 main 函数所在的线程就是这个进程中的一个线程,也称主线程。</p><p>如下图所示,在 windows 中通过查看任务管理器的方式,我们就可以清楚看到 window 当前运行的进程(.exe 文件的运行)。</p><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/%E8%BF%9B%E7%A8%8B%E7%A4%BA%E4%BE%8B%E5%9B%BE%E7%89%87-Windows.png" alt="进程示例图片-Windows"></p><h3 id="1-2-何为线程"><a href="#1-2-何为线程" class="headerlink" title="1.2. 何为线程?"></a>1.2. 何为线程?</h3><p>线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享进程的<strong>堆</strong>和<strong>方法区</strong>资源,但每个线程有自己的<strong>程序计数器</strong>、<strong>虚拟机栈</strong>和<strong>本地方法栈</strong>,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。</p><p>Java 程序天生就是多线程程序,我们可以通过 JMX 来看一下一个普通的 Java 程序有哪些线程,代码如下。</p><pre class=" language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">MultiThread</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// 获取 Java 线程管理 MXBean</span> ThreadMXBean threadMXBean <span class="token operator">=</span> ManagementFactory<span class="token punctuation">.</span><span class="token function">getThreadMXBean</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 不需要获取同步的 monitor 和 synchronizer 信息,仅获取线程和线程堆栈信息</span> ThreadInfo<span class="token punctuation">[</span><span class="token punctuation">]</span> threadInfos <span class="token operator">=</span> threadMXBean<span class="token punctuation">.</span><span class="token function">dumpAllThreads</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">,</span> <span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 遍历线程信息,仅打印线程 ID 和线程名称信息</span> <span class="token keyword">for</span> <span class="token punctuation">(</span>ThreadInfo threadInfo <span class="token operator">:</span> threadInfos<span class="token punctuation">)</span> <span class="token punctuation">{</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"["</span> <span class="token operator">+</span> threadInfo<span class="token punctuation">.</span><span class="token function">getThreadId</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">"] "</span> <span class="token operator">+</span> threadInfo<span class="token punctuation">.</span><span class="token function">getThreadName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">}</span></code></pre><p>上述程序输出如下(输出内容可能不同,不用太纠结下面每个线程的作用,只用知道 main 线程执行 main 方法即可):</p><pre><code>[5] Attach Listener //添加事件[4] Signal Dispatcher // 分发处理给 JVM 信号的线程[3] Finalizer //调用对象 finalize 方法的线程[2] Reference Handler //清除 reference 线程[1] main //main 线程,程序入口</code></pre><p>从上面的输出内容可以看出:<strong>一个 Java 程序的运行是 main 线程和多个其他线程同时运行</strong>。</p><h2 id="2-请简要描述线程与进程的关系-区别及优缺点?"><a href="#2-请简要描述线程与进程的关系-区别及优缺点?" class="headerlink" title="2. 请简要描述线程与进程的关系,区别及优缺点?"></a>2. 请简要描述线程与进程的关系,区别及优缺点?</h2><p><strong>从 JVM 角度说进程和线程之间的关系</strong></p><h3 id="2-1-图解进程和线程的关系"><a href="#2-1-图解进程和线程的关系" class="headerlink" title="2.1. 图解进程和线程的关系"></a>2.1. 图解进程和线程的关系</h3><p>下图是 Java 内存区域,通过下图我们从 JVM 的角度来说一下线程和进程之间的关系。如果你对 Java 内存区域 (运行时数据区) 这部分知识不太了解的话可以阅读一下这篇文章:<a href="https://github.com/Snailclimb/JavaGuide/blob/3965c02cc0f294b0bd3580df4868d5e396959e2e/Java%E7%9B%B8%E5%85%B3/%E5%8F%AF%E8%83%BD%E6%98%AF%E6%8A%8AJava%E5%86%85%E5%AD%98%E5%8C%BA%E5%9F%9F%E8%AE%B2%E7%9A%84%E6%9C%80%E6%B8%85%E6%A5%9A%E7%9A%84%E4%B8%80%E7%AF%87%E6%96%87%E7%AB%A0.md" target="_blank" rel="noopener" title="《可能是把 Java 内存区域讲的最清楚的一篇文章》">《可能是把 Java 内存区域讲的最清楚的一篇文章》</a></p><div align="center"> <img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3/JVM运行时数据区域.png" width="600px"/></div><p>从上图可以看出:一个进程中可以有多个线程,多个线程共享进程的<strong>堆</strong>和<strong>方法区 (JDK1.8 之后的元空间)</strong>资源,但是每个线程有自己的<strong>程序计数器</strong>、<strong>虚拟机栈</strong> 和 <strong>本地方法栈</strong>。</p><p><strong>总结:</strong> 线程 是 进程 划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。线程执行开销小,但不利于资源的管理和保护;而进程正相反</p><p>下面是该知识点的扩展内容!</p><p>下面来思考这样一个问题:为什么<strong>程序计数器</strong>、<strong>虚拟机栈</strong>和<strong>本地方法栈</strong>是线程私有的呢?为什么堆和方法区是线程共享的呢?</p><h3 id="2-2-程序计数器为什么是私有的"><a href="#2-2-程序计数器为什么是私有的" class="headerlink" title="2.2. 程序计数器为什么是私有的?"></a>2.2. 程序计数器为什么是私有的?</h3><p>程序计数器主要有下面两个作用:</p><ol><li>字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。</li><li>在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。</li></ol><p>需要注意的是,如果执行的是 native 方法,那么程序计数器记录的是 undefined 地址,只有执行的是 Java 代码时程序计数器记录的才是下一条指令的地址。</p><p>所以,程序计数器私有主要是为了<strong>线程切换后能恢复到正确的执行位置</strong>。</p><h3 id="2-3-虚拟机栈和本地方法栈为什么是私有的"><a href="#2-3-虚拟机栈和本地方法栈为什么是私有的" class="headerlink" title="2.3. 虚拟机栈和本地方法栈为什么是私有的?"></a>2.3. 虚拟机栈和本地方法栈为什么是私有的?</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="2-4-一句话简单了解堆和方法区"><a href="#2-4-一句话简单了解堆和方法区" class="headerlink" title="2.4. 一句话简单了解堆和方法区"></a>2.4. 一句话简单了解堆和方法区</h3><p>堆和方法区是所有线程共享的资源,其中堆是进程中最大的一块内存,主要用于存放新创建的对象 (所有对象都在这里分配内存),方法区主要用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。</p><h2 id="3-说说并发与并行的区别"><a href="#3-说说并发与并行的区别" class="headerlink" title="3. 说说并发与并行的区别?"></a>3. 说说并发与并行的区别?</h2><ul><li><strong>并发:</strong> 同一时间段,多个任务都在执行 (单位时间内不一定同时执行);</li><li><strong>并行:</strong> 单位时间内,多个任务同时执行。</li></ul><h2 id="4-为什么要使用多线程呢"><a href="#4-为什么要使用多线程呢" class="headerlink" title="4. 为什么要使用多线程呢?"></a>4. 为什么要使用多线程呢?</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="5-使用多线程可能带来什么问题"><a href="#5-使用多线程可能带来什么问题" class="headerlink" title="5. 使用多线程可能带来什么问题?"></a>5. 使用多线程可能带来什么问题?</h2><p>并发编程的目的就是为了能提高程序的执行效率提高程序运行速度,但是并发编程并不总是能提高程序运行速度的,而且并发编程可能会遇到很多问题,比如:内存泄漏、上下文切换、死锁还有受限于硬件和软件的资源闲置问题。</p><h2 id="6-说说线程的生命周期和状态"><a href="#6-说说线程的生命周期和状态" class="headerlink" title="6. 说说线程的生命周期和状态?"></a>6. 说说线程的生命周期和状态?</h2><p>Java 线程在运行的生命周期中的指定时刻只可能处于下面 6 种不同状态的其中一个状态(图源《Java 并发编程艺术》4.1.4 节)。</p><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/19-1-29/Java%E7%BA%BF%E7%A8%8B%E7%9A%84%E7%8A%B6%E6%80%81.png" alt="Java 线程的状态 "></p><p>线程在生命周期中并不是固定处于某一个状态而是随着代码的执行在不同状态之间切换。Java 线程状态变迁如下图所示(图源《Java 并发编程艺术》4.1.4 节):</p><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/19-1-29/Java+%E7%BA%BF%E7%A8%8B%E7%8A%B6%E6%80%81%E5%8F%98%E8%BF%81.png" alt="Java 线程状态变迁 "></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/" target="_blank" rel="noopener" title="HowToDoInJava">HowToDoInJava</a>:<a href="https://howtodoinjava.com/java/multi-threading/java-thread-life-cycle-and-thread-states/" target="_blank" rel="noopener" title="Java Thread Life Cycle and Thread States">Java Thread Life Cycle and Thread States</a>),所以 Java 系统一般将这两个状态统称为 <strong>RUNNABLE(运行中)</strong> 状态 。</p></blockquote><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3/RUNNABLE-VS-RUNNING.png" alt="RUNNABLE-VS-RUNNING"></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="7-什么是上下文切换"><a href="#7-什么是上下文切换" class="headerlink" title="7. 什么是上下文切换?"></a>7. 什么是上下文切换?</h2><p>多线程编程中一般线程的个数都大于 CPU 核心的个数,而一个 CPU 核心在任意时刻只能被一个线程使用,为了让这些线程都能得到有效执行,CPU 采取的策略是为每个线程分配时间片并轮转的形式。当一个线程的时间片用完的时候就会重新处于就绪状态让给其他线程使用,这个过程就属于一次上下文切换。</p><p>概括来说就是:当前任务在执行完 CPU 时间片切换到另一个任务之前会先保存自己的状态,以便下次再切换回这个任务时,可以再加载这个任务的状态。<strong>任务从保存到再加载的过程就是一次上下文切换</strong>。</p><p>上下文切换通常是计算密集型的。也就是说,它需要相当可观的处理器时间,在每秒几十上百次的切换中,每次切换都需要纳秒量级的时间。所以,上下文切换对系统来说意味着消耗大量的 CPU 时间,事实上,可能是操作系统中时间消耗最大的操作。</p><p>Linux 相比与其他操作系统(包括其他类 Unix 系统)有很多的优点,其中有一项就是,其上下文切换和模式切换的时间消耗非常少。</p><h2 id="8-什么是线程死锁-如何避免死锁"><a href="#8-什么是线程死锁-如何避免死锁" class="headerlink" title="8. 什么是线程死锁?如何避免死锁?"></a>8. 什么是线程死锁?如何避免死锁?</h2><h3 id="8-1-认识线程死锁"><a href="#8-1-认识线程死锁" class="headerlink" title="8.1. 认识线程死锁"></a>8.1. 认识线程死锁</h3><p>多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。</p><p>如下图所示,线程 A 持有资源 2,线程 B 持有资源 1,他们同时都想申请对方的资源,所以这两个线程就会互相等待而进入死锁状态。</p><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-4/2019-4%E6%AD%BB%E9%94%811.png" alt="线程死锁示意图 "></p><p>下面通过一个例子来说明线程死锁,代码模拟了上图的死锁的情况 (代码来源于《并发编程之美》):</p><pre class=" language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">DeadLockDemo</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">static</span> Object resource1 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Object</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//资源 1</span> <span class="token keyword">private</span> <span class="token keyword">static</span> Object resource2 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Object</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//资源 2</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span> <span class="token keyword">synchronized</span> <span class="token punctuation">(</span>resource1<span class="token punctuation">)</span> <span class="token punctuation">{</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">"get resource1"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> Thread<span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">1000</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">InterruptedException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span> e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">"waiting get resource2"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">synchronized</span> <span class="token punctuation">(</span>resource2<span class="token punctuation">)</span> <span class="token punctuation">{</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">"get resource2"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token string">"线程 1"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span> <span class="token keyword">synchronized</span> <span class="token punctuation">(</span>resource2<span class="token punctuation">)</span> <span class="token punctuation">{</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">"get resource2"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> Thread<span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">1000</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">InterruptedException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span> e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">"waiting get resource1"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">synchronized</span> <span class="token punctuation">(</span>resource1<span class="token punctuation">)</span> <span class="token punctuation">{</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">"get resource1"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token string">"线程 2"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">}</span></code></pre><p>Output</p><pre><code>Thread[线程 1,5,main]get resource1Thread[线程 2,5,main]get resource2Thread[线程 1,5,main]waiting get resource2Thread[线程 2,5,main]waiting get resource1</code></pre><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="8-2-如何避免线程死锁"><a href="#8-2-如何避免线程死锁" class="headerlink" title="8.2. 如何避免线程死锁?"></a>8.2. 如何避免线程死锁?</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><pre class=" language-java"><code class="language-java"> <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span> <span class="token keyword">synchronized</span> <span class="token punctuation">(</span>resource1<span class="token punctuation">)</span> <span class="token punctuation">{</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">"get resource1"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> Thread<span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">1000</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">InterruptedException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span> e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">"waiting get resource2"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">synchronized</span> <span class="token punctuation">(</span>resource2<span class="token punctuation">)</span> <span class="token punctuation">{</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">"get resource2"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token string">"线程 2"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><p>Output</p><pre><code>Thread[线程 1,5,main]get resource1Thread[线程 1,5,main]waiting get resource2Thread[线程 1,5,main]get resource2Thread[线程 2,5,main]get resource1Thread[线程 2,5,main]waiting get resource2Thread[线程 2,5,main]get resource2Process finished with exit code 0</code></pre><p>我们分析一下上面的代码为什么避免了死锁的发生?</p><p>线程 1 首先获得到 resource1 的监视器锁,这时候线程 2 就获取不到了。然后线程 1 再去获取 resource2 的监视器锁,可以获取到。然后线程 1 释放了对 resource1、resource2 的监视器锁的占用,线程 2 获取到就可以执行了。这样就破坏了破坏循环等待条件,因此避免了死锁。</p><h2 id="9-说说-sleep-方法和-wait-方法区别和共同点"><a href="#9-说说-sleep-方法和-wait-方法区别和共同点" class="headerlink" title="9. 说说 sleep() 方法和 wait() 方法区别和共同点?"></a>9. 说说 sleep() 方法和 wait() 方法区别和共同点?</h2><ul><li>两者最主要的区别在于:<strong>sleep 方法没有释放锁,而 wait 方法释放了锁</strong> 。</li><li>两者都可以暂停线程的执行。</li><li>Wait 通常被用于线程间交互/通信,sleep 通常被用于暂停执行。</li><li>wait() 方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的 notify() 或者 notifyAll() 方法。sleep() 方法执行完成后,线程会自动苏醒。或者可以使用 wait(long timeout)超时后线程会自动苏醒。</li></ul><h2 id="10-为什么我们调用-start-方法时会执行-run-方法,为什么我们不能直接调用-run-方法?"><a href="#10-为什么我们调用-start-方法时会执行-run-方法,为什么我们不能直接调用-run-方法?" class="headerlink" title="10. 为什么我们调用 start() 方法时会执行 run() 方法,为什么我们不能直接调用 run() 方法?"></a>10. 为什么我们调用 start() 方法时会执行 run() 方法,为什么我们不能直接调用 run() 方法?</h2><p>这是另一个非常经典的 java 多线程面试问题,而且在面试中会经常被问到。很简单,但是很多人都会答不上来!</p><p>new 一个 Thread,线程进入了新建状态;调用 start() 方法,会启动一个线程并使线程进入了就绪状态,当分配到时间片后就可以开始运行了。 start() 会执行线程的相应准备工作,然后自动执行 run() 方法的内容,这是真正的多线程工作。 而直接执行 run() 方法,会把 run 方法当成一个 main 线程下的普通方法去执行,并不会在某个线程中执行它,所以这并不是多线程工作。</p><p><strong>总结: 调用 start 方法方可启动线程并使线程进入就绪状态,而 run 方法只是 thread 的一个普通方法调用,还是在主线程里执行。</strong></p><h2 id="公众号"><a href="#公众号" class="headerlink" title="公众号"></a>公众号</h2><p>如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。</p><p><strong>《Java 面试突击》:</strong> 由本文档衍生的专为面试而生的《Java 面试突击》V2.0 PDF 版本<a href="#公众号" title="公众号">公众号</a>后台回复 <strong>“面试突击”</strong> 即可免费领取!</p><p><strong>Java 工程师必备学习资源:</strong> 一些 Java 工程师常用学习资源公众号后台回复关键字 <strong>“1”</strong> 即可免费无套路获取。</p><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/167598cd2e17b8ec.png" alt="我的公众号"></p>]]></content>
<categories>
<category> Java多线程 </category>
</categories>
<tags>
<tag> Java </tag>
<tag> 多线程 </tag>
</tags>
</entry>
<entry>
<title>搞定JVM面试之JVM类文件结构</title>
<link href="/2019/08/25/java/jvm/%E7%B1%BB%E6%96%87%E4%BB%B6%E7%BB%93%E6%9E%84/"/>
<url>/2019/08/25/java/jvm/%E7%B1%BB%E6%96%87%E4%BB%B6%E7%BB%93%E6%9E%84/</url>
<content type="html"><![CDATA[<h1 id="类文件结构"><a href="#类文件结构" class="headerlink" title="类文件结构"></a>类文件结构</h1><h2 id="一-概述"><a href="#一-概述" class="headerlink" title="一 概述"></a>一 概述</h2><p>在 Java 中,JVM 可以理解的代码就叫做<code>字节码</code>(即扩展名为 <code>.class</code> 的文件),它不面向任何特定的处理器,只面向虚拟机。Java 语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以 Java 程序运行时比较高效,而且,由于字节码并不针对一种特定的机器,因此,Java 程序无须重新编译便可在多种不同操作系统的计算机上运行。</p><p>Clojure(Lisp 语言的一种方言)、Groovy、Scala 等语言都是运行在 Java 虚拟机之上。下图展示了不同的语言被不同的编译器编译成<code>.class</code>文件最终运行在 Java 虚拟机之上。<code>.class</code>文件的二进制格式可以使用 <a href="https://www.x-ways.net/winhex/" target="_blank" rel="noopener">WinHex</a> 查看。</p><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/bg/desktop%E7%B1%BB%E6%96%87%E4%BB%B6%E7%BB%93%E6%9E%84%E6%A6%82%E8%A7%88.png" alt="java虚拟机"></p><p><strong>可以说<code>.class</code>文件是不同的语言在 Java 虚拟机之间的重要桥梁,同时也是支持 Java 跨平台很重要的一个原因。</strong></p><h2 id="二-Class-文件结构总结"><a href="#二-Class-文件结构总结" class="headerlink" title="二 Class 文件结构总结"></a>二 Class 文件结构总结</h2><p>根据 Java 虚拟机规范,类文件由单个 ClassFile 结构组成:</p><pre class=" language-java"><code class="language-java">ClassFile <span class="token punctuation">{</span> u4 magic<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//Class 文件的标志</span> u2 minor_version<span class="token punctuation">;</span><span class="token comment" spellcheck="true">//Class 的小版本号</span> u2 major_version<span class="token punctuation">;</span><span class="token comment" spellcheck="true">//Class 的大版本号</span> u2 constant_pool_count<span class="token punctuation">;</span><span class="token comment" spellcheck="true">//常量池的数量</span> cp_info constant_pool<span class="token punctuation">[</span>constant_pool_count<span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//常量池</span> u2 access_flags<span class="token punctuation">;</span><span class="token comment" spellcheck="true">//Class 的访问标记</span> u2 this_class<span class="token punctuation">;</span><span class="token comment" spellcheck="true">//当前类</span> u2 super_class<span class="token punctuation">;</span><span class="token comment" spellcheck="true">//父类</span> u2 interfaces_count<span class="token punctuation">;</span><span class="token comment" spellcheck="true">//接口</span> u2 interfaces<span class="token punctuation">[</span>interfaces_count<span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//一个类可以实现多个接口</span> u2 fields_count<span class="token punctuation">;</span><span class="token comment" spellcheck="true">//Class 文件的字段属性</span> field_info fields<span class="token punctuation">[</span>fields_count<span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//一个类会可以有个字段</span> u2 methods_count<span class="token punctuation">;</span><span class="token comment" spellcheck="true">//Class 文件的方法数量</span> method_info methods<span class="token punctuation">[</span>methods_count<span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//一个类可以有个多个方法</span> u2 attributes_count<span class="token punctuation">;</span><span class="token comment" spellcheck="true">//此类的属性表中的属性数</span> attribute_info attributes<span class="token punctuation">[</span>attributes_count<span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//属性表集合</span><span class="token punctuation">}</span></code></pre><p>下面详细介绍一下 Class 文件结构涉及到的一些组件。</p><p><strong>Class文件字节码结构组织示意图</strong> (之前在网上保存的,非常不错,原出处不明):</p><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/%E7%B1%BB%E6%96%87%E4%BB%B6%E5%AD%97%E8%8A%82%E7%A0%81%E7%BB%93%E6%9E%84%E7%BB%84%E7%BB%87%E7%A4%BA%E6%84%8F%E5%9B%BE.png" alt="类文件字节码结构组织示意图"></p><h3 id="2-1-魔数"><a href="#2-1-魔数" class="headerlink" title="2.1 魔数"></a>2.1 魔数</h3><pre class=" language-java"><code class="language-java"> u4 magic<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//Class 文件的标志</span></code></pre><p>每个 Class 文件的头四个字节称为魔数(Magic Number),它的唯一作用是<strong>确定这个文件是否为一个能被虚拟机接收的 Class 文件</strong>。 </p><p>程序设计者很多时候都喜欢用一些特殊的数字表示固定的文件类型或者其它特殊的含义。</p><h3 id="2-2-Class-文件版本"><a href="#2-2-Class-文件版本" class="headerlink" title="2.2 Class 文件版本"></a>2.2 Class 文件版本</h3><pre class=" language-java"><code class="language-java"> u2 minor_version<span class="token punctuation">;</span><span class="token comment" spellcheck="true">//Class 的小版本号</span> u2 major_version<span class="token punctuation">;</span><span class="token comment" spellcheck="true">//Class 的大版本号</span></code></pre><p>紧接着魔数的四个字节存储的是 Class 文件的版本号:第五和第六是<strong>次版本号</strong>,第七和第八是<strong>主版本号</strong>。</p><p>高版本的 Java 虚拟机可以执行低版本编译器生成的 Class 文件,但是低版本的 Java 虚拟机不能执行高版本编译器生成的 Class 文件。所以,我们在实际开发的时候要确保开发的的 JDK 版本和生产环境的 JDK 版本保持一致。</p><h3 id="2-3-常量池"><a href="#2-3-常量池" class="headerlink" title="2.3 常量池"></a>2.3 常量池</h3><pre class=" language-java"><code class="language-java"> u2 constant_pool_count<span class="token punctuation">;</span><span class="token comment" spellcheck="true">//常量池的数量</span> cp_info constant_pool<span class="token punctuation">[</span>constant_pool_count<span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//常量池</span></code></pre><p>紧接着主次版本号之后的是常量池,常量池的数量是 constant_pool_count-1(<strong>常量池计数器是从1开始计数的,将第0项常量空出来是有特殊考虑的,索引值为0代表“不引用任何一个常量池项”</strong>)。</p><p>常量池主要存放两大常量:字面量和符号引用。字面量比较接近于 Java 语言层面的的常量概念,如文本字符串、声明为 final 的常量值等。而符号引用则属于编译原理方面的概念。包括下面三类常量: </p><ul><li>类和接口的全限定名 </li><li>字段的名称和描述符 </li><li>方法的名称和描述符</li></ul><p>常量池中每一项常量都是一个表,这14种表有一个共同的特点:<strong>开始的第一位是一个 u1 类型的标志位 -tag 来标识常量的类型,代表当前这个常量属于哪种常量类型.</strong></p><table><thead><tr><th align="center">类型</th><th align="center">标志(tag)</th><th align="center">描述</th></tr></thead><tbody><tr><td align="center">CONSTANT_utf8_info</td><td align="center">1</td><td align="center">UTF-8编码的字符串</td></tr><tr><td align="center">CONSTANT_Integer_info</td><td align="center">3</td><td align="center">整形字面量</td></tr><tr><td align="center">CONSTANT_Float_info</td><td align="center">4</td><td align="center">浮点型字面量</td></tr><tr><td align="center">CONSTANT_Long_info</td><td align="center">5</td><td align="center">长整型字面量</td></tr><tr><td align="center">CONSTANT_Double_info</td><td align="center">6</td><td align="center">双精度浮点型字面量</td></tr><tr><td align="center">CONSTANT_Class_info</td><td align="center">7</td><td align="center">类或接口的符号引用</td></tr><tr><td align="center">CONSTANT_String_info</td><td align="center">8</td><td align="center">字符串类型字面量</td></tr><tr><td align="center">CONSTANT_Fieldref_info</td><td align="center">9</td><td align="center">字段的符号引用</td></tr><tr><td align="center">CONSTANT_Methodref_info</td><td align="center">10</td><td align="center">类中方法的符号引用</td></tr><tr><td align="center">CONSTANT_InterfaceMethodref_info</td><td align="center">11</td><td align="center">接口中方法的符号引用</td></tr><tr><td align="center">CONSTANT_NameAndType_info</td><td align="center">12</td><td align="center">字段或方法的符号引用</td></tr><tr><td align="center">CONSTANT_MothodType_info</td><td align="center">16</td><td align="center">标志方法类型</td></tr><tr><td align="center">CONSTANT_MethodHandle_info</td><td align="center">15</td><td align="center">表示方法句柄</td></tr><tr><td align="center">CONSTANT_InvokeDynamic_info</td><td align="center">18</td><td align="center">表示一个动态方法调用点</td></tr></tbody></table><p><code>.class</code> 文件可以通过<code>javap -v class类名</code> 指令来看一下其常量池中的信息(<code>javap -v class类名-&gt; temp.txt</code> :将结果输出到 temp.txt 文件)。</p><h3 id="2-4-访问标志"><a href="#2-4-访问标志" class="headerlink" title="2.4 访问标志"></a>2.4 访问标志</h3><p>在常量池结束之后,紧接着的两个字节代表访问标志,这个标志用于识别一些类或者接口层次的访问信息,包括:这个 Class 是类还是接口,是否为 public 或者 abstract 类型,如果是类的话是否声明为 final 等等。</p><p>类访问和属性修饰符:</p><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/%E8%AE%BF%E9%97%AE%E6%A0%87%E5%BF%97.png" alt="类访问和属性修饰符"></p><p>我们定义了一个 Employee 类</p><pre class=" language-java"><code class="language-java"><span class="token keyword">package</span> top<span class="token punctuation">.</span>snailclimb<span class="token punctuation">.</span>bean<span class="token punctuation">;</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Employee</span> <span class="token punctuation">{</span> <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">}</span></code></pre><p>通过<code>javap -v class类名</code> 指令来看一下类的访问标志。</p><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/%E6%9F%A5%E7%9C%8B%E7%B1%BB%E7%9A%84%E8%AE%BF%E9%97%AE%E6%A0%87%E5%BF%97.png" alt="查看类的访问标志"></p><h3 id="2-5-当前类索引-父类索引与接口索引集合"><a href="#2-5-当前类索引-父类索引与接口索引集合" class="headerlink" title="2.5 当前类索引,父类索引与接口索引集合"></a>2.5 当前类索引,父类索引与接口索引集合</h3><pre class=" language-java"><code class="language-java"> u2 this_class<span class="token punctuation">;</span><span class="token comment" spellcheck="true">//当前类</span> u2 super_class<span class="token punctuation">;</span><span class="token comment" spellcheck="true">//父类</span> u2 interfaces_count<span class="token punctuation">;</span><span class="token comment" spellcheck="true">//接口</span> u2 interfaces<span class="token punctuation">[</span>interfaces_count<span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//一个雷可以实现多个接口</span></code></pre><p><strong>类索引用于确定这个类的全限定名,父类索引用于确定这个类的父类的全限定名,由于 Java 语言的单继承,所以父类索引只有一个,除了 <code>java.lang.Object</code> 之外,所有的 java 类都有父类,因此除了 <code>java.lang.Object</code> 外,所有 Java 类的父类索引都不为 0。</strong></p><p><strong>接口索引集合用来描述这个类实现了那些接口,这些被实现的接口将按<code>implents</code>(如果这个类本身是接口的话则是<code>extends</code>) 后的接口顺序从左到右排列在接口索引集合中。</strong></p><h3 id="2-6-字段表集合"><a href="#2-6-字段表集合" class="headerlink" title="2.6 字段表集合"></a>2.6 字段表集合</h3><pre class=" language-java"><code class="language-java"> u2 fields_count<span class="token punctuation">;</span><span class="token comment" spellcheck="true">//Class 文件的字段的个数</span> field_info fields<span class="token punctuation">[</span>fields_count<span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//一个类会可以有个字段</span></code></pre><p>字段表(field info)用于描述接口或类中声明的变量。字段包括类级变量以及实例变量,但不包括在方法内部声明的局部变量。</p><p><strong>field info(字段表) 的结构:</strong></p><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/%E5%AD%97%E6%AE%B5%E8%A1%A8%E7%9A%84%E7%BB%93%E6%9E%84.png" alt="字段表的结构 "></p><ul><li><strong>access_flags:</strong> 字段的作用域(<code>public</code> ,<code>private</code>,<code>protected</code>修饰符),是实例变量还是类变量(<code>static</code>修饰符),可否被序列化(transient 修饰符),可变性(final),可见性(volatile 修饰符,是否强制从主内存读写)。</li><li><strong>name_index:</strong> 对常量池的引用,表示的字段的名称;</li><li><strong>descriptor_index:</strong> 对常量池的引用,表示字段和方法的描述符;</li><li><strong>attributes_count:</strong> 一个字段还会拥有一些额外的属性,attributes_count 存放属性的个数;</li><li><strong>attributes[attributes_count]:</strong> 存放具体属性具体内容。</li></ul><p>上述这些信息中,各个修饰符都是布尔值,要么有某个修饰符,要么没有,很适合使用标志位来表示。而字段叫什么名字、字段被定义为什么数据类型这些都是无法固定的,只能引用常量池中常量来描述。</p><p><strong>字段的 access_flags 的取值:</strong></p><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/%E5%AD%97%E6%AE%B5%E7%9A%84access_flags%E7%9A%84%E5%8F%96%E5%80%BC.png" alt="字段的access_flags的取值"></p><h3 id="2-7-方法表集合"><a href="#2-7-方法表集合" class="headerlink" title="2.7 方法表集合"></a>2.7 方法表集合</h3><pre class=" language-java"><code class="language-java"> u2 methods_count<span class="token punctuation">;</span><span class="token comment" spellcheck="true">//Class 文件的方法的数量</span> method_info methods<span class="token punctuation">[</span>methods_count<span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//一个类可以有个多个方法</span></code></pre><p>methods_count 表示方法的数量,而 method_info 表示的方法表。</p><p>Class 文件存储格式中对方法的描述与对字段的描述几乎采用了完全一致的方式。方法表的结构如同字段表一样,依次包括了访问标志、名称索引、描述符索引、属性表集合几项。 </p><p><strong>method_info(方法表的) 结构:</strong></p><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/%E6%96%B9%E6%B3%95%E8%A1%A8%E7%9A%84%E7%BB%93%E6%9E%84.png" alt="方法表的结构"></p><p><strong>方法表的 access_flag 取值:</strong></p><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/%E6%96%B9%E6%B3%95%E8%A1%A8%E7%9A%84access_flag%E7%9A%84%E6%89%80%E6%9C%89%E6%A0%87%E5%BF%97%E4%BD%8D.png" alt="方法表的 access_flag 取值"></p><p>注意:因为<code>volatile</code>修饰符和<code>transient</code>修饰符不可以修饰方法,所以方法表的访问标志中没有这两个对应的标志,但是增加了<code>synchronized</code>、<code>native</code>、<code>abstract</code>等关键字修饰方法,所以也就多了这些关键字对应的标志。</p><h3 id="2-8-属性表集合"><a href="#2-8-属性表集合" class="headerlink" title="2.8 属性表集合"></a>2.8 属性表集合</h3><pre class=" language-java"><code class="language-java"> u2 attributes_count<span class="token punctuation">;</span><span class="token comment" spellcheck="true">//此类的属性表中的属性数</span> attribute_info attributes<span class="token punctuation">[</span>attributes_count<span class="token punctuation">]</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//属性表集合</span></code></pre><p>在 Class 文件,字段表,方法表中都可以携带自己的属性表集合,以用于描述某些场景专有的信息。与 Class 文件中其它的数据项目要求的顺序、长度和内容不同,属性表集合的限制稍微宽松一些,不再要求各个属性表具有严格的顺序,并且只要不与已有的属性名重复,任何人实现的编译器都可以向属性表中写 入自己定义的属性信息,Java 虚拟机运行时会忽略掉它不认识的属性。</p><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li><a href="https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html" target="_blank" rel="noopener">https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html</a></li><li><a href="https://coolshell.cn/articles/9229.html" target="_blank" rel="noopener">https://coolshell.cn/articles/9229.html</a></li><li><a href="https://blog.csdn.net/luanlouis/article/details/39960815" target="_blank" rel="noopener">https://blog.csdn.net/luanlouis/article/details/39960815</a></li><li>《实战 Java 虚拟机》</li></ul><h2 id="公众号"><a href="#公众号" class="headerlink" title="公众号"></a>公众号</h2><p>如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。</p><p><strong>《Java面试突击》:</strong> 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本<a href="#公众号">公众号</a>后台回复 <strong>“Java面试突击”</strong> 即可免费领取!</p><p><strong>Java工程师必备学习资源:</strong> 一些Java工程师常用学习资源<a href="#公众号">公众号</a>后台回复关键字 <strong>“1”</strong> 即可免费无套路获取。 </p><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/167598cd2e17b8ec.png" alt="我的公众号"></p>]]></content>
<categories>
<category> JVM </category>
</categories>
<tags>
<tag> Java </tag>
<tag> JVM </tag>
</tags>
</entry>
<entry>
<title>搞定JVM面试之JVM类加载过程</title>
<link href="/2019/08/25/java/jvm/%E7%B1%BB%E5%8A%A0%E8%BD%BD%E8%BF%87%E7%A8%8B/"/>
<url>/2019/08/25/java/jvm/%E7%B1%BB%E5%8A%A0%E8%BD%BD%E8%BF%87%E7%A8%8B/</url>
<content type="html"><![CDATA[<h1 id="类加载过程"><a href="#类加载过程" class="headerlink" title="类加载过程"></a>类加载过程</h1><p>Class 文件需要加载到虚拟机中之后才能运行和使用,那么虚拟机是如何加载这些 Class 文件呢?</p><p>系统加载 Class 类型的文件主要三步:<strong>加载-&gt;连接-&gt;初始化</strong>。连接过程又可分为三步:<strong>验证-&gt;准备-&gt;解析</strong>。</p><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/%E7%B1%BB%E5%8A%A0%E8%BD%BD%E8%BF%87%E7%A8%8B.png" alt="类加载过程"></p><h2 id="加载"><a href="#加载" class="headerlink" title="加载"></a>加载</h2><p>类加载过程的第一步,主要完成下面3件事情:</p><ol><li>通过全类名获取定义此类的二进制字节流</li><li>将字节流所代表的静态存储结构转换为方法区的运行时数据结构</li><li>在内存中生成一个代表该类的 Class 对象,作为方法区这些数据的访问入口</li></ol><p>虚拟机规范多上面这3点并不具体,因此是非常灵活的。比如:”通过全类名获取定义此类的二进制字节流” 并没有指明具体从哪里获取、怎样获取。比如:比较常见的就是从 ZIP 包中读取(日后出现的JAR、EAR、WAR格式的基础)、其他文件生成(典型应用就是JSP)等等。</p><p><strong>一个非数组类的加载阶段(加载阶段获取类的二进制字节流的动作)是可控性最强的阶段,这一步我们可以去完成还可以自定义类加载器去控制字节流的获取方式(重写一个类加载器的 <code>loadClass()</code> 方法)。数组类型不通过类加载器创建,它由 Java 虚拟机直接创建。</strong></p><p>类加载器、双亲委派模型也是非常重要的知识点,这部分内容会在后面的文章中单独介绍到。</p><p>加载阶段和连接阶段的部分内容是交叉进行的,加载阶段尚未结束,连接阶段可能就已经开始了。</p><h2 id="验证"><a href="#验证" class="headerlink" title="验证"></a>验证</h2><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/%E9%AA%8C%E8%AF%81%E9%98%B6%E6%AE%B5.png" alt="验证阶段示意图"></p><h2 id="准备"><a href="#准备" class="headerlink" title="准备"></a>准备</h2><p><strong>准备阶段是正式为类变量分配内存并设置类变量初始值的阶段</strong>,这些内存都将在方法区中分配。对于该阶段有以下几点需要注意:</p><ol><li>这时候进行内存分配的仅包括类变量(static),而不包括实例变量,实例变量会在对象实例化时随着对象一块分配在 Java 堆中。</li><li>这里所设置的初始值”通常情况”下是数据类型默认的零值(如0、0L、null、false等),比如我们定义了<code>public static int value=111</code> ,那么 value 变量在准备阶段的初始值就是 0 而不是111(初始化阶段才会复制)。特殊情况:比如给 value 变量加上了 fianl 关键字<code>public static final int value=111</code> ,那么准备阶段 value 的值就被复制为 111。</li></ol><p><strong>基本数据类型的零值:</strong></p><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/%E5%9F%BA%E6%9C%AC%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E7%9A%84%E9%9B%B6%E5%80%BC.png" alt="基本数据类型的零值"></p><h2 id="解析"><a href="#解析" class="headerlink" title="解析"></a>解析</h2><p>解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用限定符7类符号引用进行。</p><p>符号引用就是一组符号来描述目标,可以是任何字面量。<strong>直接引用</strong>就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。在程序实际运行时,只有符号引用是不够的,举个例子:在程序执行方法时,系统需要明确知道这个方法所在的位置。Java 虚拟机为每个类都准备了一张方法表来存放类中所有的方法。当需要调用一个类的方法的时候,只要知道这个方法在方发表中的偏移量就可以直接调用该方法了。通过解析操作符号引用就可以直接转变为目标方法在类中方法表的位置,从而使得方法可以被调用。</p><p>综上,解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,也就是得到类或者字段、方法在内存中的指针或者偏移量。</p><h2 id="初始化"><a href="#初始化" class="headerlink" title="初始化"></a>初始化</h2><p>初始化是类加载的最后一步,也是真正执行类中定义的 Java 程序代码(字节码),初始化阶段是执行类构造器 <code>&lt;clinit&gt; ()</code>方法的过程。</p><p>对于<code>&lt;clinit&gt;()</code> 方法的调用,虚拟机会自己确保其在多线程环境中的安全性。因为 <code>&lt;clinit&gt;()</code> 方法是带锁线程安全,所以在多线程环境下进行类初始化的话可能会引起死锁,并且这种死锁很难被发现。</p><p>对于初始化阶段,虚拟机严格规范了有且只有5种情况下,必须对类进行初始化:</p><ol><li>当遇到 new 、 getstatic、putstatic或invokestatic 这4条直接码指令时,比如 new 一个类,读取一个静态字段(未被 final 修饰)、或调用一个类的静态方法时。</li><li>使用 <code>java.lang.reflect</code> 包的方法对类进行反射调用时 ,如果类没初始化,需要触发其初始化。</li><li>初始化一个类,如果其父类还未初始化,则先触发该父类的初始化。</li><li>当虚拟机启动时,用户需要定义一个要执行的主类 (包含 main 方法的那个类),虚拟机会先初始化这个类。</li><li>当使用 JDK1.7 的动态动态语言时,如果一个 MethodHandle 实例的最后解析结构为 REF_getStatic、REF_putStatic、REF_invokeStatic、的方法句柄,并且这个句柄没有初始化,则需要先触发器初始化。</li></ol><p><strong>参考</strong></p><ul><li>《深入理解Java虚拟机》</li><li>《实战Java虚拟机》</li><li><a href="https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-5.html" target="_blank" rel="noopener">https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-5.html</a></li></ul><h2 id="公众号"><a href="#公众号" class="headerlink" title="公众号"></a>公众号</h2><p>如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。</p><p><strong>《Java面试突击》:</strong> 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本<a href="#公众号">公众号</a>后台回复 <strong>“Java面试突击”</strong> 即可免费领取!</p><p><strong>Java工程师必备学习资源:</strong> 一些Java工程师常用学习资源<a href="#公众号">公众号</a>后台回复关键字 <strong>“1”</strong> 即可免费无套路获取。 </p><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/167598cd2e17b8ec.png" alt="我的公众号"></p>]]></content>
<categories>
<category> JVM </category>
</categories>
<tags>
<tag> Java </tag>
<tag> JVM </tag>
</tags>
</entry>
<entry>
<title>搞定JVM面试之JVM 类加载器</title>
<link href="/2019/08/25/java/jvm/%E7%B1%BB%E5%8A%A0%E8%BD%BD%E5%99%A8/"/>
<url>/2019/08/25/java/jvm/%E7%B1%BB%E5%8A%A0%E8%BD%BD%E5%99%A8/</url>
<content type="html"><![CDATA[<h2 id="回顾一下类加载过程"><a href="#回顾一下类加载过程" class="headerlink" title="回顾一下类加载过程"></a>回顾一下类加载过程</h2><p>类加载过程:<strong>加载-&gt;连接-&gt;初始化</strong>。连接过程又可分为三步:<strong>验证-&gt;准备-&gt;解析</strong>。</p><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/%E7%B1%BB%E5%8A%A0%E8%BD%BD%E8%BF%87%E7%A8%8B.png" alt="类加载过程"></p><p>一个非数组类的加载阶段(加载阶段获取类的二进制字节流的动作)是可控性最强的阶段,这一步我们可以去完成还可以自定义类加载器去控制字节流的获取方式(重写一个类加载器的 <code>loadClass()</code> 方法)。数组类型不通过类加载器创建,它由 Java 虚拟机直接创建。</p><p>所有的类都由类加载器加载,加载的作用就是将 .class文件加载到内存。</p><h2 id="类加载器总结"><a href="#类加载器总结" class="headerlink" title="类加载器总结"></a>类加载器总结</h2><p>JVM 中内置了三个重要的 ClassLoader,除了 BootstrapClassLoader 其他类加载器均由 Java 实现且全部继承自<code>java.lang.ClassLoader</code>:</p><ol><li><strong>BootstrapClassLoader(启动类加载器)</strong> :最顶层的加载类,由C++实现,负责加载 <code>%JAVA_HOME%/lib</code>目录下的jar包和类或者或被 <code>-Xbootclasspath</code>参数指定的路径中的所有类。</li><li><strong>ExtensionClassLoader(扩展类加载器)</strong> :主要负责加载目录 <code>%JRE_HOME%/lib/ext</code> 目录下的jar包和类,或被 <code>java.ext.dirs</code> 系统变量所指定的路径下的jar包。</li><li><strong>AppClassLoader(应用程序类加载器)</strong> :面向我们用户的加载器,负责加载当前应用classpath下的所有jar包和类。</li></ol><h2 id="双亲委派模型"><a href="#双亲委派模型" class="headerlink" title="双亲委派模型"></a>双亲委派模型</h2><h3 id="双亲委派模型介绍"><a href="#双亲委派模型介绍" class="headerlink" title="双亲委派模型介绍"></a>双亲委派模型介绍</h3><p>每一个类都有一个对应它的类加载器。系统中的 ClassLoder 在协同工作的时候会默认使用 <strong>双亲委派模型</strong> 。即在类加载的时候,系统会首先判断当前类是否被加载过。已经被加载的类会直接返回,否则才会尝试加载。加载的时候,首先会把该请求委派该父类加载器的 <code>loadClass()</code> 处理,因此所有的请求最终都应该传送到顶层的启动类加载器 <code>BootstrapClassLoader</code> 中。当父类加载器无法处理时,才由自己来处理。当父类加载器为null时,会使用启动类加载器 <code>BootstrapClassLoader</code> 作为父类加载器。</p><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/classloader_WPS%E5%9B%BE%E7%89%87.png" alt="ClassLoader"></p><p>每个类加载都有一个父类加载器,我们通过下面的程序来验证。</p><pre class=" language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ClassLoaderDemo</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"ClassLodarDemo's ClassLoader is "</span> <span class="token operator">+</span> ClassLoaderDemo<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">.</span><span class="token function">getClassLoader</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"The Parent of ClassLodarDemo's ClassLoader is "</span> <span class="token operator">+</span> ClassLoaderDemo<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">.</span><span class="token function">getClassLoader</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getParent</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"The GrandParent of ClassLodarDemo's ClassLoader is "</span> <span class="token operator">+</span> ClassLoaderDemo<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">.</span><span class="token function">getClassLoader</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getParent</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getParent</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">}</span></code></pre><p>Output</p><pre><code>ClassLodarDemo&#39;s ClassLoader is sun.misc.Launcher$AppClassLoader@18b4aac2The Parent of ClassLodarDemo&#39;s ClassLoader is sun.misc.Launcher$ExtClassLoader@1b6d3586The GrandParent of ClassLodarDemo&#39;s ClassLoader is null</code></pre><p><code>AppClassLoader</code>的父类加载器为<code>ExtClassLoader</code><br><code>ExtClassLoader</code>的父类加载器为null,<strong>null并不代表<code>ExtClassLoader</code>没有父类加载器,而是 <code>BootstrapClassLoader</code></strong> 。</p><p>其实这个双亲翻译的容易让别人误解,我们一般理解的双亲都是父母,这里的双亲更多地表达的是“父母这一辈”的人而已,并不是说真的有一个 Mother ClassLoader 和一个 Father ClassLoader 。另外,类加载器之间的“父子”关系也不是通过继承来体现的,是由“优先级”来决定。官方API文档对这部分的描述如下:</p><blockquote><p>The Java platform uses a delegation model for loading classes. <strong>The basic idea is that every class loader has a “parent” class loader.</strong> When loading a class, a class loader first “delegates” the search for the class to its parent class loader before attempting to find the class itself.</p></blockquote><h3 id="双亲委派模型实现源码分析"><a href="#双亲委派模型实现源码分析" class="headerlink" title="双亲委派模型实现源码分析"></a>双亲委派模型实现源码分析</h3><p>双亲委派模型的实现代码非常简单,逻辑非常清晰,都集中在 <code>java.lang.ClassLoader</code> 的 <code>loadClass()</code> 中,相关代码如下所示。</p><pre class=" language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">final</span> ClassLoader parent<span class="token punctuation">;</span> <span class="token keyword">protected</span> Class<span class="token operator">&lt;</span><span class="token operator">?</span><span class="token operator">></span> <span class="token function">loadClass</span><span class="token punctuation">(</span>String name<span class="token punctuation">,</span> <span class="token keyword">boolean</span> resolve<span class="token punctuation">)</span> <span class="token keyword">throws</span> ClassNotFoundException <span class="token punctuation">{</span> <span class="token keyword">synchronized</span> <span class="token punctuation">(</span><span class="token function">getClassLoadingLock</span><span class="token punctuation">(</span>name<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// 首先,检查请求的类是否已经被加载过</span> Class<span class="token operator">&lt;</span><span class="token operator">?</span><span class="token operator">></span> c <span class="token operator">=</span> <span class="token function">findLoadedClass</span><span class="token punctuation">(</span>name<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>c <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">long</span> t0 <span class="token operator">=</span> System<span class="token punctuation">.</span><span class="token function">nanoTime</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>parent <span class="token operator">!=</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token comment" spellcheck="true">//父加载器不为空,调用父加载器loadClass()方法处理</span> c <span class="token operator">=</span> parent<span class="token punctuation">.</span><span class="token function">loadClass</span><span class="token punctuation">(</span>name<span class="token punctuation">,</span> <span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span><span class="token comment" spellcheck="true">//父加载器为空,使用启动类加载器 BootstrapClassLoader 加载</span> c <span class="token operator">=</span> <span class="token function">findBootstrapClassOrNull</span><span class="token punctuation">(</span>name<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">ClassNotFoundException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">//抛出异常说明父类加载器无法完成加载请求</span> <span class="token punctuation">}</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>c <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">long</span> t1 <span class="token operator">=</span> System<span class="token punctuation">.</span><span class="token function">nanoTime</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//自己尝试加载</span> c <span class="token operator">=</span> <span class="token function">findClass</span><span class="token punctuation">(</span>name<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// this is the defining class loader; record the stats</span> sun<span class="token punctuation">.</span>misc<span class="token punctuation">.</span>PerfCounter<span class="token punctuation">.</span><span class="token function">getParentDelegationTime</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">addTime</span><span class="token punctuation">(</span>t1 <span class="token operator">-</span> t0<span class="token punctuation">)</span><span class="token punctuation">;</span> sun<span class="token punctuation">.</span>misc<span class="token punctuation">.</span>PerfCounter<span class="token punctuation">.</span><span class="token function">getFindClassTime</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">addElapsedTimeFrom</span><span class="token punctuation">(</span>t1<span class="token punctuation">)</span><span class="token punctuation">;</span> sun<span class="token punctuation">.</span>misc<span class="token punctuation">.</span>PerfCounter<span class="token punctuation">.</span><span class="token function">getFindClasses</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">increment</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>resolve<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">resolveClass</span><span class="token punctuation">(</span>c<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> c<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre><h3 id="双亲委派模型的好处"><a href="#双亲委派模型的好处" class="headerlink" title="双亲委派模型的好处"></a>双亲委派模型的好处</h3><p>双亲委派模型保证了Java程序的稳定运行,可以避免类的重复加载(JVM 区分不同类的方式不仅仅根据类名,相同的类文件被不同的类加载器加载产生的是两个不同的类),也保证了 Java 的核心 API 不被篡改。如果没有使用双亲委派模型,而是每个类加载器加载自己的话就会出现一些问题,比如我们编写一个称为 <code>java.lang.Object</code> 类的话,那么程序运行的时候,系统就会出现多个不同的 <code>Object</code> 类。</p><h3 id="如果我们不想用双亲委派模型怎么办?"><a href="#如果我们不想用双亲委派模型怎么办?" class="headerlink" title="如果我们不想用双亲委派模型怎么办?"></a>如果我们不想用双亲委派模型怎么办?</h3><p>为了避免双亲委托机制,我们可以自己定义一个类加载器,然后重写 <code>loadClass()</code> 即可。</p><h2 id="自定义类加载器"><a href="#自定义类加载器" class="headerlink" title="自定义类加载器"></a>自定义类加载器</h2><p>除了 <code>BootstrapClassLoader</code> 其他类加载器均由 Java 实现且全部继承自<code>java.lang.ClassLoader</code>。如果我们要自定义自己的类加载器,很明显需要继承 <code>ClassLoader</code>。</p><h2 id="推荐阅读"><a href="#推荐阅读" class="headerlink" title="推荐阅读"></a>推荐阅读</h2><ul><li><a href="https://blog.csdn.net/xyang81/article/details/7292380" target="_blank" rel="noopener">https://blog.csdn.net/xyang81/article/details/7292380</a></li><li><a href="https://juejin.im/post/5c04892351882516e70dcc9b" target="_blank" rel="noopener">https://juejin.im/post/5c04892351882516e70dcc9b</a></li><li><a href="http://gityuan.com/2016/01/24/java-classloader/" target="_blank" rel="noopener">http://gityuan.com/2016/01/24/java-classloader/</a></li></ul><h3 id="公众号"><a href="#公众号" class="headerlink" title="公众号"></a>公众号</h3><p>如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。</p><p><strong>《Java面试突击》:</strong> 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本<a href="#公众号">公众号</a>后台回复 <strong>“Java面试突击”</strong> 即可免费领取!</p><p><strong>Java工程师必备学习资源:</strong> 一些Java工程师常用学习资源<a href="#公众号">公众号</a>后台回复关键字 <strong>“1”</strong> 即可免费无套路获取。 </p><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/167598cd2e17b8ec.png" alt="我的公众号"></p>]]></content>
<categories>
<category> JVM </category>
</categories>
<tags>
<tag> Java </tag>
<tag> JVM </tag>
</tags>
</entry>
<entry>
<title>搞定JVM面试之JVM 垃圾回收</title>
<link href="/2019/08/25/java/jvm/JVM%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6/"/>
<url>/2019/08/25/java/jvm/JVM%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6/</url>
<content type="html"><![CDATA[<h1 id="JVM-垃圾回收"><a href="#JVM-垃圾回收" class="headerlink" title="JVM 垃圾回收"></a>JVM 垃圾回收</h1><h2 id="写在前面"><a href="#写在前面" class="headerlink" title="写在前面"></a>写在前面</h2><h3 id="本节常见面试题"><a href="#本节常见面试题" class="headerlink" title="本节常见面试题"></a>本节常见面试题</h3><p>问题答案在文中都有提到</p><ul><li>如何判断对象是否死亡(两种方法)。</li><li>简单的介绍一下强引用、软引用、弱引用、虚引用(虚引用与软引用和弱引用的区别、使用软引用能带来的好处)。</li><li>如何判断一个常量是废弃常量</li><li>如何判断一个类是无用的类</li><li>垃圾收集有哪些算法,各自的特点?</li><li>HotSpot 为什么要分为新生代和老年代?</li><li>常见的垃圾回收器有那些?</li><li>介绍一下 CMS,G1 收集器。</li><li>Minor Gc 和 Full GC 有什么不同呢?</li></ul><h3 id="本文导火索"><a href="#本文导火索" class="headerlink" title="本文导火索"></a>本文导火索</h3><p><img src="http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-26/29176325.jpg" alt=""></p><p>当需要排查各种内存溢出问题、当垃圾收集成为系统达到更高并发的瓶颈时,我们就需要对这些“自动化”的技术实施必要的监控和调节。</p><h2 id="1-揭开-JVM-内存分配与回收的神秘面纱"><a href="#1-揭开-JVM-内存分配与回收的神秘面纱" class="headerlink" title="1 揭开 JVM 内存分配与回收的神秘面纱"></a>1 揭开 JVM 内存分配与回收的神秘面纱</h2><p>Java 的自动内存管理主要是针对对象内存的回收和对象内存的分配。同时,Java 自动内存管理最核心的功能是 <strong>堆</strong> 内存中对象的分配与回收。</p><p>Java 堆是垃圾收集器管理的主要区域,因此也被称作<strong>GC 堆(Garbage Collected Heap)</strong>.从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法,所以 Java 堆还可以细分为:新生代和老年代:再细致一点有:Eden 空间、From Survivor、To Survivor 空间等。<strong>进一步划分的目的是更好地回收内存,或者更快地分配内存。</strong></p><p><strong>堆空间的基本结构:</strong></p><div align="center"> <img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3堆结构.png" width="400px"/></div><p>上图所示的 eden 区、s0(“From”) 区、s1(“To”) 区都属于新生代,tentired 区属于老年代。大部分情况,对象都会首先在 Eden 区域分配,在一次新生代垃圾回收后,如果对象还存活,则会进入 s1(“To”),并且对象的年龄还会加 1(Eden 区-&gt;Survivor 区后对象的初始年龄变为 1),当它的年龄增加到一定程度(默认为 15 岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 <code>-XX:MaxTenuringThreshold</code> 来设置。经过这次GC后,Eden区和”From”区已经被清空。这个时候,”From”和”To”会交换他们的角色,也就是新的”To”就是上次GC前的“From”,新的”From”就是上次GC前的”To”。不管怎样,都会保证名为To的Survivor区域是空的。Minor GC会一直重复这样的过程,直到“To”区被填满,”To”区被填满之后,会将所有对象移动到年老代中。</p><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/%E5%A0%86%E5%86%85%E5%AD%98.jpg" alt="堆内存常见分配策略 "></p><h3 id="1-1-对象优先在-eden-区分配"><a href="#1-1-对象优先在-eden-区分配" class="headerlink" title="1.1 对象优先在 eden 区分配"></a>1.1 对象优先在 eden 区分配</h3><p>目前主流的垃圾收集器都会采用分代回收算法,因此需要将堆内存分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。</p><p>大多数情况下,对象在新生代中 eden 区分配。当 eden 区没有足够空间进行分配时,虚拟机将发起一次 Minor GC.下面我们来进行实际测试以下。</p><p>在测试之前我们先来看看 <strong>Minor GC 和 Full GC 有什么不同呢?</strong></p><ul><li><strong>新生代 GC(Minor GC)</strong>:指发生新生代的的垃圾收集动作,Minor GC 非常频繁,回收速度一般也比较快。</li><li><strong>老年代 GC(Major GC/Full GC)</strong>:指发生在老年代的 GC,出现了 Major GC 经常会伴随至少一次的 Minor GC(并非绝对),Major GC 的速度一般会比 Minor GC 的慢 10 倍以上。</li></ul><p><strong>测试:</strong></p><pre class=" language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">GCTest</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">byte</span><span class="token punctuation">[</span><span class="token punctuation">]</span> allocation1<span class="token punctuation">,</span> allocation2<span class="token punctuation">;</span> allocation1 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">byte</span><span class="token punctuation">[</span><span class="token number">30900</span><span class="token operator">*</span><span class="token number">1024</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//allocation2 = new byte[900*1024];</span> <span class="token punctuation">}</span><span class="token punctuation">}</span></code></pre><p>通过以下方式运行:<br><img src="http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-26/25178350.jpg" alt=""></p><p>添加的参数:<code>-XX:+PrintGCDetails</code><br><img src="http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-26/10317146.jpg" alt=""></p><p>运行结果 (红色字体描述有误,应该是对应于 JDK1.7 的永久代):</p><p><img src="http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-26/28954286.jpg" alt=""></p><p>从上图我们可以看出 eden 区内存几乎已经被分配完全(即使程序什么也不做,新生代也会使用 2000 多 k 内存)。假如我们再为 allocation2 分配内存会出现什么情况呢?</p><pre class=" language-java"><code class="language-java">allocation2 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">byte</span><span class="token punctuation">[</span><span class="token number">900</span><span class="token operator">*</span><span class="token number">1024</span><span class="token punctuation">]</span><span class="token punctuation">;</span></code></pre><p><img src="http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-26/28128785.jpg" alt=""></p><p><strong>简单解释一下为什么会出现这种情况:</strong> 因为给 allocation2 分配内存的时候 eden 区内存几乎已经被分配完了,我们刚刚讲了当 Eden 区没有足够空间进行分配时,虚拟机将发起一次 Minor GC.GC 期间虚拟机又发现 allocation1 无法存入 Survivor 空间,所以只好通过 <strong>分配担保机制</strong> 把新生代的对象提前转移到老年代中去,老年代上的空间足够存放 allocation1,所以不会出现 Full GC。执行 Minor GC 后,后面分配的对象如果能够存在 eden 区的话,还是会在 eden 区分配内存。可以执行如下代码验证:</p><pre class=" language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">GCTest</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">byte</span><span class="token punctuation">[</span><span class="token punctuation">]</span> allocation1<span class="token punctuation">,</span> allocation2<span class="token punctuation">,</span>allocation3<span class="token punctuation">,</span>allocation4<span class="token punctuation">,</span>allocation5<span class="token punctuation">;</span> allocation1 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">byte</span><span class="token punctuation">[</span><span class="token number">32000</span><span class="token operator">*</span><span class="token number">1024</span><span class="token punctuation">]</span><span class="token punctuation">;</span> allocation2 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">byte</span><span class="token punctuation">[</span><span class="token number">1000</span><span class="token operator">*</span><span class="token number">1024</span><span class="token punctuation">]</span><span class="token punctuation">;</span> allocation3 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">byte</span><span class="token punctuation">[</span><span class="token number">1000</span><span class="token operator">*</span><span class="token number">1024</span><span class="token punctuation">]</span><span class="token punctuation">;</span> allocation4 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">byte</span><span class="token punctuation">[</span><span class="token number">1000</span><span class="token operator">*</span><span class="token number">1024</span><span class="token punctuation">]</span><span class="token punctuation">;</span> allocation5 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">byte</span><span class="token punctuation">[</span><span class="token number">1000</span><span class="token operator">*</span><span class="token number">1024</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">}</span></code></pre><h3 id="1-2-大对象直接进入老年代"><a href="#1-2-大对象直接进入老年代" class="headerlink" title="1.2 大对象直接进入老年代"></a>1.2 大对象直接进入老年代</h3><p>大对象就是需要大量连续内存空间的对象(比如:字符串、数组)。</p><p><strong>为什么要这样呢?</strong></p><p>为了避免为大对象分配内存时由于分配担保机制带来的复制而降低效率。</p><h3 id="1-3-长期存活的对象将进入老年代"><a href="#1-3-长期存活的对象将进入老年代" class="headerlink" title="1.3 长期存活的对象将进入老年代"></a>1.3 长期存活的对象将进入老年代</h3><p>既然虚拟机采用了分代收集的思想来管理内存,那么内存回收时就必须能识别哪些对象应放在新生代,哪些对象应放在老年代中。为了做到这一点,虚拟机给每个对象一个对象年龄(Age)计数器。</p><p>如果对象在 Eden 出生并经过第一次 Minor GC 后仍然能够存活,并且能被 Survivor 容纳的话,将被移动到 Survivor 空间中,并将对象年龄设为 1.对象在 Survivor 中每熬过一次 MinorGC,年龄就增加 1 岁,当它的年龄增加到一定程度(默认为 15 岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 <code>-XX:MaxTenuringThreshold</code> 来设置。</p><h3 id="1-4-动态对象年龄判定"><a href="#1-4-动态对象年龄判定" class="headerlink" title="1.4 动态对象年龄判定"></a>1.4 动态对象年龄判定</h3><p>为了更好的适应不同程序的内存情况,虚拟机不是永远要求对象年龄必须达到了某个值才能进入老年代,如果 Survivor 空间中相同年龄所有对象大小的总和大于 Survivor 空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无需达到要求的年龄。</p><h2 id="2-对象已经死亡?"><a href="#2-对象已经死亡?" class="headerlink" title="2 对象已经死亡?"></a>2 对象已经死亡?</h2><p>堆中几乎放着所有的对象实例,对堆垃圾回收前的第一步就是要判断那些对象已经死亡(即不能再被任何途径使用的对象)。</p><p><img src="http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/11034259.jpg" alt=""></p><h3 id="2-1-引用计数法"><a href="#2-1-引用计数法" class="headerlink" title="2.1 引用计数法"></a>2.1 引用计数法</h3><p>给对象中添加一个引用计数器,每当有一个地方引用它,计数器就加 1;当引用失效,计数器就减 1;任何时候计数器为 0 的对象就是不可能再被使用的。</p><p><strong>这个方法实现简单,效率高,但是目前主流的虚拟机中并没有选择这个算法来管理内存,其最主要的原因是它很难解决对象之间相互循环引用的问题。</strong> 所谓对象之间的相互引用问题,如下面代码所示:除了对象 objA 和 objB 相互引用着对方之外,这两个对象之间再无任何引用。但是他们因为互相引用对方,导致它们的引用计数器都不为 0,于是引用计数算法无法通知 GC 回收器回收他们。</p><pre class=" language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ReferenceCountingGc</span> <span class="token punctuation">{</span> Object instance <span class="token operator">=</span> null<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span> ReferenceCountingGc objA <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ReferenceCountingGc</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> ReferenceCountingGc objB <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ReferenceCountingGc</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> objA<span class="token punctuation">.</span>instance <span class="token operator">=</span> objB<span class="token punctuation">;</span> objB<span class="token punctuation">.</span>instance <span class="token operator">=</span> objA<span class="token punctuation">;</span> objA <span class="token operator">=</span> null<span class="token punctuation">;</span> objB <span class="token operator">=</span> null<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">}</span></code></pre><h3 id="2-2-可达性分析算法"><a href="#2-2-可达性分析算法" class="headerlink" title="2.2 可达性分析算法"></a>2.2 可达性分析算法</h3><p>这个算法的基本思想就是通过一系列的称为 <strong>“GC Roots”</strong> 的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的。</p><p><img src="http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/72762049.jpg" alt="可达性分析算法 "></p><h3 id="2-3-再谈引用"><a href="#2-3-再谈引用" class="headerlink" title="2.3 再谈引用"></a>2.3 再谈引用</h3><p>无论是通过引用计数法判断对象引用数量,还是通过可达性分析法判断对象的引用链是否可达,判定对象的存活都与“引用”有关。</p><p>JDK1.2 之前,Java 中引用的定义很传统:如果 reference 类型的数据存储的数值代表的是另一块内存的起始地址,就称这块内存代表一个引用。</p><p>JDK1.2 以后,Java 对引用的概念进行了扩充,将引用分为强引用、软引用、弱引用、虚引用四种(引用强度逐渐减弱)</p><p><strong>1.强引用(StrongReference)</strong></p><p>以前我们使用的大部分引用实际上都是强引用,这是使用最普遍的引用。如果一个对象具有强引用,那就类似于<strong>必不可少的生活用品</strong>,垃圾回收器绝不会回收它。当内存空间不足,Java 虚拟机宁愿抛出 OutOfMemoryError 错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。</p><p><strong>2.软引用(SoftReference)</strong></p><p>如果一个对象只具有软引用,那就类似于<strong>可有可无的生活用品</strong>。如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。</p><p>软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,JAVA 虚拟机就会把这个软引用加入到与之关联的引用队列中。</p><p><strong>3.弱引用(WeakReference)</strong></p><p>如果一个对象只具有弱引用,那就类似于<strong>可有可无的生活用品</strong>。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。 </p><p>弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java 虚拟机就会把这个弱引用加入到与之关联的引用队列中。</p><p><strong>4.虚引用(PhantomReference)</strong></p><p>“虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。</p><p><strong>虚引用主要用来跟踪对象被垃圾回收的活动</strong>。</p><p><strong>虚引用与软引用和弱引用的一个区别在于:</strong> 虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。 </p><p>特别注意,在程序设计中一般很少使用弱引用与虚引用,使用软引用的情况较多,这是因为<strong>软引用可以加速 JVM 对垃圾内存的回收速度,可以维护系统的运行安全,防止内存溢出(OutOfMemory)等问题的产生</strong>。</p><h3 id="2-4-不可达的对象并非“非死不可”"><a href="#2-4-不可达的对象并非“非死不可”" class="headerlink" title="2.4 不可达的对象并非“非死不可”"></a>2.4 不可达的对象并非“非死不可”</h3><p>即使在可达性分析法中不可达的对象,也并非是“非死不可”的,这时候它们暂时处于“缓刑阶段”,要真正宣告一个对象死亡,至少要经历两次标记过程;可达性分析法中不可达的对象被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行 finalize 方法。当对象没有覆盖 finalize 方法,或 finalize 方法已经被虚拟机调用过时,虚拟机将这两种情况视为没有必要执行。</p><p>被判定为需要执行的对象将会被放在一个队列中进行第二次标记,除非这个对象与引用链上的任何一个对象建立关联,否则就会被真的回收。</p><h3 id="2-5-如何判断一个常量是废弃常量"><a href="#2-5-如何判断一个常量是废弃常量" class="headerlink" title="2.5 如何判断一个常量是废弃常量"></a>2.5 如何判断一个常量是废弃常量</h3><p>运行时常量池主要回收的是废弃的常量。那么,我们如何判断一个常量是废弃常量呢?</p><p>假如在常量池中存在字符串 “abc”,如果当前没有任何 String 对象引用该字符串常量的话,就说明常量 “abc” 就是废弃常量,如果这时发生内存回收的话而且有必要的话,”abc” 就会被系统清理出常量池。</p><p>注意:我们在 <a href="https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484303&idx=1&sn=af0fd436cef755463f59ee4dd0720cbd&chksm=fd9855eecaefdcf8d94ac581cfda4e16c8a730bda60c3b50bc55c124b92f23b6217f7f8e58d5&token=506869459&lang=zh_CN#rd" target="_blank" rel="noopener">可能是把 Java 内存区域讲的最清楚的一篇文章 </a> 也讲了 JDK1.7 及之后版本的 JVM 已经将运行时常量池从方法区中移了出来,在 Java 堆(Heap)中开辟了一块区域存放运行时常量池。</p><h3 id="2-6-如何判断一个类是无用的类"><a href="#2-6-如何判断一个类是无用的类" class="headerlink" title="2.6 如何判断一个类是无用的类"></a>2.6 如何判断一个类是无用的类</h3><p>方法区主要回收的是无用的类,那么如何判断一个类是无用的类的呢?</p><p>判定一个常量是否是“废弃常量”比较简单,而要判定一个类是否是“无用的类”的条件则相对苛刻许多。类需要同时满足下面 3 个条件才能算是 <strong>“无用的类”</strong> :</p><ul><li>该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。</li><li>加载该类的 ClassLoader 已经被回收。</li><li>该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。</li></ul><p>虚拟机可以对满足上述 3 个条件的无用类进行回收,这里说的仅仅是“可以”,而并不是和对象一样不使用了就会必然被回收。</p><h2 id="3-垃圾收集算法"><a href="#3-垃圾收集算法" class="headerlink" title="3 垃圾收集算法"></a>3 垃圾收集算法</h2><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/%E5%9E%83%E5%9C%BE%E6%94%B6%E9%9B%86%E7%AE%97%E6%B3%95.jpg" alt="垃圾收集算法分类"></p><h3 id="3-1-标记-清除算法"><a href="#3-1-标记-清除算法" class="headerlink" title="3.1 标记-清除算法"></a>3.1 标记-清除算法</h3><p>该算法分为“标记”和“清除”阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。它是最基础的收集算法,后续的算法都是对其不足进行改进得到。这种垃圾收集算法会带来两个明显的问题:</p><ol><li><strong>效率问题</strong></li><li><strong>空间问题(标记清除后会产生大量不连续的碎片)</strong></li></ol><img src="http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/63707281.jpg" alt="公众号" width="500px"><h3 id="3-2-复制算法"><a href="#3-2-复制算法" class="headerlink" title="3.2 复制算法"></a>3.2 复制算法</h3><p>为了解决效率问题,“复制”收集算法出现了。它可以将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。这样就使每次的内存回收都是对内存区间的一半进行回收。</p><img src="http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/90984624.jpg" alt="公众号" width="500px"><h3 id="3-3-标记-整理算法"><a href="#3-3-标记-整理算法" class="headerlink" title="3.3 标记-整理算法"></a>3.3 标记-整理算法</h3><p>根据老年代的特点提出的一种标记算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。</p><p><img src="http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/94057049.jpg" alt="标记-整理算法 "></p><h3 id="3-4-分代收集算法"><a href="#3-4-分代收集算法" class="headerlink" title="3.4 分代收集算法"></a>3.4 分代收集算法</h3><p>当前虚拟机的垃圾收集都采用分代收集算法,这种算法没有什么新的思想,只是根据对象存活周期的不同将内存分为几块。一般将 java 堆分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。</p><p><strong>比如在新生代中,每次收集都会有大量对象死去,所以可以选择复制算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集。</strong></p><p><strong>延伸面试问题:</strong> HotSpot 为什么要分为新生代和老年代?</p><p>根据上面的对分代收集算法的介绍回答。</p><h2 id="4-垃圾收集器"><a href="#4-垃圾收集器" class="headerlink" title="4 垃圾收集器"></a>4 垃圾收集器</h2><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/%E5%9E%83%E5%9C%BE%E6%94%B6%E9%9B%86%E5%99%A8.jpg" alt="垃圾收集器分类"></p><p><strong>如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。</strong></p><p>虽然我们对各个收集器进行比较,但并非要挑选出一个最好的收集器。因为直到现在为止还没有最好的垃圾收集器出现,更加没有万能的垃圾收集器,<strong>我们能做的就是根据具体应用场景选择适合自己的垃圾收集器</strong>。试想一下:如果有一种四海之内、任何场景下都适用的完美收集器存在,那么我们的 HotSpot 虚拟机就不会实现那么多不同的垃圾收集器了。</p><h3 id="4-1-Serial-收集器"><a href="#4-1-Serial-收集器" class="headerlink" title="4.1 Serial 收集器"></a>4.1 Serial 收集器</h3><p>Serial(串行)收集器收集器是最基本、历史最悠久的垃圾收集器了。大家看名字就知道这个收集器是一个单线程收集器了。它的 <strong>“单线程”</strong> 的意义不仅仅意味着它只会使用一条垃圾收集线程去完成垃圾收集工作,更重要的是它在进行垃圾收集工作的时候必须暂停其他所有的工作线程( <strong>“Stop The World”</strong> ),直到它收集结束。</p><p> <strong>新生代采用复制算法,老年代采用标记-整理算法。</strong><br><img src="http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/46873026.jpg" alt=" Serial 收集器 "></p><p>虚拟机的设计者们当然知道 Stop The World 带来的不良用户体验,所以在后续的垃圾收集器设计中停顿时间在不断缩短(仍然还有停顿,寻找最优秀的垃圾收集器的过程仍然在继续)。</p><p>但是 Serial 收集器有没有优于其他垃圾收集器的地方呢?当然有,它<strong>简单而高效(与其他收集器的单线程相比)</strong>。Serial 收集器由于没有线程交互的开销,自然可以获得很高的单线程收集效率。Serial 收集器对于运行在 Client 模式下的虚拟机来说是个不错的选择。</p><h3 id="4-2-ParNew-收集器"><a href="#4-2-ParNew-收集器" class="headerlink" title="4.2 ParNew 收集器"></a>4.2 ParNew 收集器</h3><p><strong>ParNew 收集器其实就是 Serial 收集器的多线程版本,除了使用多线程进行垃圾收集外,其余行为(控制参数、收集算法、回收策略等等)和 Serial 收集器完全一样。</strong></p><p> <strong>新生代采用复制算法,老年代采用标记-整理算法。</strong><br><img src="http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/22018368.jpg" alt="ParNew 收集器 "></p><p>它是许多运行在 Server 模式下的虚拟机的首要选择,除了 Serial 收集器外,只有它能与 CMS 收集器(真正意义上的并发收集器,后面会介绍到)配合工作。</p><p><strong>并行和并发概念补充:</strong></p><ul><li><p><strong>并行(Parallel)</strong> :指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。</p></li><li><p><strong>并发(Concurrent)</strong>:指用户线程与垃圾收集线程同时执行(但不一定是并行,可能会交替执行),用户程序在继续运行,而垃圾收集器运行在另一个 CPU 上。</p></li></ul><h3 id="4-3-Parallel-Scavenge-收集器"><a href="#4-3-Parallel-Scavenge-收集器" class="headerlink" title="4.3 Parallel Scavenge 收集器"></a>4.3 Parallel Scavenge 收集器</h3><p>Parallel Scavenge 收集器也是使用复制算法的多线程收集器,它看上去几乎和ParNew都一样。 <strong>那么它有什么特别之处呢?</strong></p><pre><code>-XX:+UseParallelGC 使用 Parallel 收集器+ 老年代串行-XX:+UseParallelOldGC 使用 Parallel 收集器+ 老年代并行</code></pre><p><strong>Parallel Scavenge 收集器关注点是吞吐量(高效率的利用 CPU)。CMS 等垃圾收集器的关注点更多的是用户线程的停顿时间(提高用户体验)。所谓吞吐量就是 CPU 中用于运行用户代码的时间与 CPU 总消耗时间的比值。</strong> Parallel Scavenge 收集器提供了很多参数供用户找到最合适的停顿时间或最大吞吐量,如果对于收集器运作不太了解的话,手工优化存在困难的话可以选择把内存管理优化交给虚拟机去完成也是一个不错的选择。</p><p> <strong>新生代采用复制算法,老年代采用标记-整理算法。</strong><br><img src="http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/22018368.jpg" alt="Parallel Scavenge 收集器 "></p><h3 id="4-4-Serial-Old-收集器"><a href="#4-4-Serial-Old-收集器" class="headerlink" title="4.4.Serial Old 收集器"></a>4.4.Serial Old 收集器</h3><p><strong>Serial 收集器的老年代版本</strong>,它同样是一个单线程收集器。它主要有两大用途:一种用途是在 JDK1.5 以及以前的版本中与 Parallel Scavenge 收集器搭配使用,另一种用途是作为 CMS 收集器的后备方案。</p><h3 id="4-5-Parallel-Old-收集器"><a href="#4-5-Parallel-Old-收集器" class="headerlink" title="4.5 Parallel Old 收集器"></a>4.5 Parallel Old 收集器</h3><p> <strong>Parallel Scavenge 收集器的老年代版本</strong>。使用多线程和“标记-整理”算法。在注重吞吐量以及 CPU 资源的场合,都可以优先考虑 Parallel Scavenge 收集器和 Parallel Old 收集器。</p><h3 id="4-6-CMS-收集器"><a href="#4-6-CMS-收集器" class="headerlink" title="4.6 CMS 收集器"></a>4.6 CMS 收集器</h3><p><strong>CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。它非常符合在注重用户体验的应用上使用。</strong></p><p><strong>CMS(Concurrent Mark Sweep)收集器是 HotSpot 虚拟机第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程(基本上)同时工作。</strong></p><p>从名字中的<strong>Mark Sweep</strong>这两个词可以看出,CMS 收集器是一种 <strong>“标记-清除”算法</strong>实现的,它的运作过程相比于前面几种垃圾收集器来说更加复杂一些。整个过程分为四个步骤:</p><ul><li><strong>初始标记:</strong> 暂停所有的其他线程,并记录下直接与 root 相连的对象,速度很快 ;</li><li><strong>并发标记:</strong> 同时开启 GC 和用户线程,用一个闭包结构去记录可达对象。但在这个阶段结束,这个闭包结构并不能保证包含当前所有的可达对象。因为用户线程可能会不断的更新引用域,所以 GC 线程无法保证可达性分析的实时性。所以这个算法里会跟踪记录这些发生引用更新的地方。</li><li><strong>重新标记:</strong> 重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短</li><li><strong>并发清除:</strong> 开启用户线程,同时 GC 线程开始对为标记的区域做清扫。</li></ul><p><img src="http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/82825079.jpg" alt="CMS 垃圾收集器 "></p><p>从它的名字就可以看出它是一款优秀的垃圾收集器,主要优点:<strong>并发收集、低停顿</strong>。但是它有下面三个明显的缺点:</p><ul><li><strong>对 CPU 资源敏感;</strong></li><li><strong>无法处理浮动垃圾;</strong></li><li><strong>它使用的回收算法-“标记-清除”算法会导致收集结束时会有大量空间碎片产生。</strong></li></ul><h3 id="4-7-G1-收集器"><a href="#4-7-G1-收集器" class="headerlink" title="4.7 G1 收集器"></a>4.7 G1 收集器</h3><p><strong>G1 (Garbage-First) 是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足 GC 停顿时间要求的同时,还具备高吞吐量性能特征.</strong></p><p>被视为 JDK1.7 中 HotSpot 虚拟机的一个重要进化特征。它具备一下特点:</p><ul><li><strong>并行与并发</strong>:G1 能充分利用 CPU、多核环境下的硬件优势,使用多个 CPU(CPU 或者 CPU 核心)来缩短 Stop-The-World 停顿时间。部分其他收集器原本需要停顿 Java 线程执行的 GC 动作,G1 收集器仍然可以通过并发的方式让 java 程序继续执行。</li><li><strong>分代收集</strong>:虽然 G1 可以不需要其他收集器配合就能独立管理整个 GC 堆,但是还是保留了分代的概念。</li><li><strong>空间整合</strong>:与 CMS 的“标记–清理”算法不同,G1 从整体来看是基于“标记整理”算法实现的收集器;从局部上来看是基于“复制”算法实现的。</li><li><strong>可预测的停顿</strong>:这是 G1 相对于 CMS 的另一个大优势,降低停顿时间是 G1 和 CMS 共同的关注点,但 G1 除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为 M 毫秒的时间片段内。</li></ul><p>G1 收集器的运作大致分为以下几个步骤:</p><ul><li><strong>初始标记</strong></li><li><strong>并发标记</strong></li><li><strong>最终标记</strong></li><li><strong>筛选回收</strong></li></ul><p><strong>G1 收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的 Region(这也就是它的名字 Garbage-First 的由来)</strong>。这种使用 Region 划分内存空间以及有优先级的区域回收方式,保证了 GF 收集器在有限时间内可以尽可能高的收集效率(把内存化整为零)。</p><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li>《深入理解 Java 虚拟机:JVM 高级特性与最佳实践(第二版》</li><li><a href="https://my.oschina.net/hosee/blog/644618" target="_blank" rel="noopener">https://my.oschina.net/hosee/blog/644618</a></li><li><a href="https://docs.oracle.com/javase/specs/jvms/se8/html/index.html" target="_blank" rel="noopener">https://docs.oracle.com/javase/specs/jvms/se8/html/index.html</a></li></ul><h2 id="公众号"><a href="#公众号" class="headerlink" title="公众号"></a>公众号</h2><p>如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。</p><p><strong>《Java面试突击》:</strong> 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本<a href="#公众号">公众号</a>后台回复 <strong>“Java面试突击”</strong> 即可免费领取!</p><p><strong>Java工程师必备学习资源:</strong> 一些Java工程师常用学习资源<a href="#公众号">公众号</a>后台回复关键字 <strong>“1”</strong> 即可免费无套路获取。 </p><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/167598cd2e17b8ec.png" alt="我的公众号"></p>]]></content>
<categories>
<category> JVM </category>
</categories>
<tags>
<tag> Java </tag>
<tag> JVM </tag>
</tags>
</entry>
<entry>
<title>搞定JVM面试之JVM 知识点汇总</title>
<link href="/2019/08/25/java/jvm/jvm%20%E7%9F%A5%E8%AF%86%E7%82%B9%E6%B1%87%E6%80%BB/"/>
<url>/2019/08/25/java/jvm/jvm%20%E7%9F%A5%E8%AF%86%E7%82%B9%E6%B1%87%E6%80%BB/</url>
<content type="html"><![CDATA[<p>无论什么级别的Java从业者,JVM都是进阶时必须迈过的坎。不管是工作还是面试中,JVM都是必考题。如果不懂JVM的话,薪酬会非常吃亏(近70%的面试者挂在JVM上了)。</p><p>掌握了JVM机制,就等于学会了深层次解决问题的方法。对于Java开发者而言,只有熟悉底层虚拟机的运行机制,才能通过JVM日志深入到字节码的层次去分析排查问题,发现隐性的系统缺陷,进而提升系统性能。</p><p>一些技术人员开发工具用得很熟练,触及JVM问题时却是模棱两可,甚至连内存模型和内存区域,HotSpot和JVM规范,都混淆不清。工作很长时间,在生产时还在用缺省参数来直接启动,以致系统运行时出现性能、稳定性等问题时束手无措,不知该如何追踪排查。久而久之,这对自己的职业成长是极为不利的。</p><p>掌握JVM,是深入Java技术栈的必经之路。</p><p><img src="https://i.loli.net/2019/09/10/HsJXU8S4oVtCTM7.png" alt="jv.png"></p>]]></content>
<categories>
<category> JVM </category>
</categories>
<tags>
<tag> Java </tag>
<tag> JVM </tag>
</tags>
</entry>
<entry>
<title>搞定JVM面试之JDK 监控和故障处理工具总结</title>
<link href="/2019/08/25/java/jvm/JDK%E7%9B%91%E6%8E%A7%E5%92%8C%E6%95%85%E9%9A%9C%E5%A4%84%E7%90%86%E5%B7%A5%E5%85%B7%E6%80%BB%E7%BB%93/"/>
<url>/2019/08/25/java/jvm/JDK%E7%9B%91%E6%8E%A7%E5%92%8C%E6%95%85%E9%9A%9C%E5%A4%84%E7%90%86%E5%B7%A5%E5%85%B7%E6%80%BB%E7%BB%93/</url>
<content type="html"><![CDATA[<h1 id="JDK-监控和故障处理工具总结"><a href="#JDK-监控和故障处理工具总结" class="headerlink" title="JDK 监控和故障处理工具总结"></a>JDK 监控和故障处理工具总结</h1><h2 id="JDK-命令行工具"><a href="#JDK-命令行工具" class="headerlink" title="JDK 命令行工具"></a>JDK 命令行工具</h2><p>这些命令在 JDK 安装目录下的 bin 目录下:</p><ul><li><strong><code>jps</code></strong> (JVM Process Status): 类似 UNIX 的 <code>ps</code> 命令。用户查看所有 Java 进程的启动类、传入参数和 Java 虚拟机参数等信息;</li><li><strong><code>jstat</code></strong>( JVM Statistics Monitoring Tool): 用于收集 HotSpot 虚拟机各方面的运行数据;</li><li><strong><code>jinfo</code></strong> (Configuration Info for Java) : Configuration Info forJava,显示虚拟机配置信息;</li><li><strong><code>jmap</code></strong> (Memory Map for Java) :生成堆转储快照;</li><li><strong><code>jhat</code></strong> (JVM Heap Dump Browser ) : 用于分析 heapdump 文件,它会建立一个 HTTP/HTML 服务器,让用户可以在浏览器上查看分析结果;</li><li><strong><code>jstack</code></strong> (Stack Trace for Java):生成虚拟机当前时刻的线程快照,线程快照就是当前虚拟机内每一条线程正在执行的方法堆栈的集合。</li></ul><h3 id="jps-查看所有-Java-进程"><a href="#jps-查看所有-Java-进程" class="headerlink" title="jps:查看所有 Java 进程"></a><code>jps</code>:查看所有 Java 进程</h3><p><code>jps</code>(JVM Process Status) 命令类似 UNIX 的 <code>ps</code> 命令。</p><p><code>jps</code>:显示虚拟机执行主类名称以及这些进程的本地虚拟机唯一 ID(Local Virtual Machine Identifier,LVMID)。<code>jps -q</code> :只输出进程的本地虚拟机唯一 ID。</p><pre class=" language-powershell"><code class="language-powershell">C:\Users\SnailClimb>jps7360 NettyClient2173967972 Launcher16504 Jps17340 NettyServer</code></pre><p><code>jps -l</code>:输出主类的全名,如果进程执行的是 Jar 包,输出 Jar 路径。</p><pre class=" language-powershell"><code class="language-powershell">C:\Users\SnailClimb>jps <span class="token operator">-</span>l7360 firstNettyDemo<span class="token punctuation">.</span>NettyClient2173967972 org<span class="token punctuation">.</span>jetbrains<span class="token punctuation">.</span>jps<span class="token punctuation">.</span>cmdline<span class="token punctuation">.</span>Launcher16492 sun<span class="token punctuation">.</span>tools<span class="token punctuation">.</span>jps<span class="token punctuation">.</span>Jps17340 firstNettyDemo<span class="token punctuation">.</span>NettyServer</code></pre><p><code>jps -v</code>:输出虚拟机进程启动时 JVM 参数。</p><p><code>jps -m</code>:输出传递给 Java 进程 main() 函数的参数。</p><h3 id="jstat-监视虚拟机各种运行状态信息"><a href="#jstat-监视虚拟机各种运行状态信息" class="headerlink" title="jstat: 监视虚拟机各种运行状态信息"></a><code>jstat</code>: 监视虚拟机各种运行状态信息</h3><p>jstat(JVM Statistics Monitoring Tool) 使用于监视虚拟机各种运行状态信息的命令行工具。 它可以显示本地或者远程(需要远程主机提供 RMI 支持)虚拟机进程中的类信息、内存、垃圾收集、JIT 编译等运行数据,在没有 GUI,只提供了纯文本控制台环境的服务器上,它将是运行期间定位虚拟机性能问题的首选工具。</p><p><strong><code>jstat</code> 命令使用格式:</strong></p><pre class=" language-powershell"><code class="language-powershell">jstat <span class="token operator">-</span>&lt;option> <span class="token punctuation">[</span><span class="token operator">-</span>t<span class="token punctuation">]</span> <span class="token punctuation">[</span><span class="token operator">-</span>h&lt;lines><span class="token punctuation">]</span> &lt;vmid> <span class="token punctuation">[</span>&lt;interval> <span class="token punctuation">[</span>&lt;count><span class="token punctuation">]</span><span class="token punctuation">]</span></code></pre><p>比如 <code>jstat -gc -h3 31736 1000 10</code>表示分析进程 id 为 31736 的 gc 情况,每隔 1000ms 打印一次记录,打印 10 次停止,每 3 行后打印指标头部。</p><p><strong>常见的 option 如下:</strong></p><ul><li><code>jstat -class vmid</code> :显示 ClassLoader 的相关信息;</li><li><code>jstat -compiler vmid</code> :显示 JIT 编译的相关信息;</li><li><code>jstat -gc vmid</code> :显示与 GC 相关的堆信息;</li><li><code>jstat -gccapacity vmid</code> :显示各个代的容量及使用情况;</li><li><code>jstat -gcnew vmid</code> :显示新生代信息;</li><li><code>jstat -gcnewcapcacity vmid</code> :显示新生代大小与使用情况;</li><li><code>jstat -gcold vmid</code> :显示老年代和永久代的信息;</li><li><code>jstat -gcoldcapacity vmid</code> :显示老年代的大小;</li><li><code>jstat -gcpermcapacity vmid</code> :显示永久代大小;</li><li><code>jstat -gcutil vmid</code> :显示垃圾收集信息;</li></ul><p>另外,加上 <code>-t</code>参数可以在输出信息上加一个 Timestamp 列,显示程序的运行时间。</p><h3 id="jinfo-实时地查看和调整虚拟机各项参数"><a href="#jinfo-实时地查看和调整虚拟机各项参数" class="headerlink" title="jinfo: 实时地查看和调整虚拟机各项参数"></a><code>jinfo</code>: 实时地查看和调整虚拟机各项参数</h3><p><code>jinfo vmid</code> :输出当前 jvm 进程的全部参数和系统属性 (第一部分是系统的属性,第二部分是 JVM 的参数)。</p><p><code>jinfo -flag name vmid</code> :输出对应名称的参数的具体值。比如输出 MaxHeapSize、查看当前 jvm 进程是否开启打印 GC 日志 ( <code>-XX:PrintGCDetails</code> :详细 GC 日志模式,这两个都是默认关闭的)。</p><pre class=" language-powershell"><code class="language-powershell">C:\Users\SnailClimb>jinfo <span class="token operator">-</span>flag MaxHeapSize 17340<span class="token operator">-</span>XX:MaxHeapSize=2124414976C:\Users\SnailClimb>jinfo <span class="token operator">-</span>flag PrintGC 17340<span class="token operator">-</span>XX:<span class="token operator">-</span>PrintGC</code></pre><p>使用 jinfo 可以在不重启虚拟机的情况下,可以动态的修改 jvm 的参数。尤其在线上的环境特别有用,请看下面的例子:</p><p><code>jinfo -flag [+|-]name vmid</code> 开启或者关闭对应名称的参数。</p><pre class=" language-powershell"><code class="language-powershell">C:\Users\SnailClimb>jinfo <span class="token operator">-</span>flag PrintGC 17340<span class="token operator">-</span>XX:<span class="token operator">-</span>PrintGCC:\Users\SnailClimb>jinfo <span class="token operator">-</span>flag <span class="token operator">+</span>PrintGC 17340C:\Users\SnailClimb>jinfo <span class="token operator">-</span>flag PrintGC 17340<span class="token operator">-</span>XX:<span class="token operator">+</span>PrintGC</code></pre><h3 id="jmap-生成堆转储快照"><a href="#jmap-生成堆转储快照" class="headerlink" title="jmap:生成堆转储快照"></a><code>jmap</code>:生成堆转储快照</h3><p><code>jmap</code>(Memory Map for Java)命令用于生成堆转储快照。 如果不使用 <code>jmap</code> 命令,要想获取 Java 堆转储,可以使用 <code>“-XX:+HeapDumpOnOutOfMemoryError”</code> 参数,可以让虚拟机在 OOM 异常出现之后自动生成 dump 文件,Linux 命令下可以通过 <code>kill -3</code> 发送进程退出信号也能拿到 dump 文件。</p><p><code>jmap</code> 的作用并不仅仅是为了获取 dump 文件,它还可以查询 finalizer 执行队列、Java 堆和永久代的详细信息,如空间使用率、当前使用的是哪种收集器等。和<code>jinfo</code>一样,<code>jmap</code>有不少功能在 Windows 平台下也是受限制的。</p><p>示例:将指定应用程序的堆快照输出到桌面。后面,可以通过 jhat、Visual VM 等工具分析该堆文件。</p><pre class=" language-powershell"><code class="language-powershell">C:\Users\SnailClimb>jmap <span class="token operator">-</span>dump:format=b<span class="token punctuation">,</span>file=C:\Users\SnailClimb\Desktop\heap<span class="token punctuation">.</span>hprof 17340Dumping heap to C:\Users\SnailClimb\Desktop\heap<span class="token punctuation">.</span>hprof <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>Heap dump file created</code></pre><h3 id="jhat-分析-heapdump-文件"><a href="#jhat-分析-heapdump-文件" class="headerlink" title="jhat: 分析 heapdump 文件"></a><strong><code>jhat</code></strong>: 分析 heapdump 文件</h3><p> <strong><code>jhat</code></strong> 用于分析 heapdump 文件,它会建立一个 HTTP/HTML 服务器,让用户可以在浏览器上查看分析结果。</p><pre class=" language-powershell"><code class="language-powershell">C:\Users\SnailClimb>jhat C:\Users\SnailClimb\Desktop\heap<span class="token punctuation">.</span>hprofReading <span class="token keyword">from</span> C:\Users\SnailClimb\Desktop\heap<span class="token punctuation">.</span>hprof<span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>Dump file created Sat May 04 12:30:31 CST 2019Snapshot read<span class="token punctuation">,</span> resolving<span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>Resolving 131419 objects<span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>Chasing references<span class="token punctuation">,</span> expect 26 dots<span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>Eliminating duplicate references<span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>Snapshot resolved<span class="token punctuation">.</span>Started HTTP server on port 7000Server is ready<span class="token punctuation">.</span></code></pre><p>访问 <a href="http://localhost:7000/" target="_blank" rel="noopener">http://localhost:7000/</a></p><h3 id="jstack-生成虚拟机当前时刻的线程快照"><a href="#jstack-生成虚拟机当前时刻的线程快照" class="headerlink" title="jstack :生成虚拟机当前时刻的线程快照"></a><strong><code>jstack</code></strong> :生成虚拟机当前时刻的线程快照</h3><p><code>jstack</code>(Stack Trace for Java)命令用于生成虚拟机当前时刻的线程快照。线程快照就是当前虚拟机内每一条线程正在执行的方法堆栈的集合.</p><p>生成线程快照的目的主要是定位线程长时间出现停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等都是导致线程长时间停顿的原因。线程出现停顿的时候通过<code>jstack</code>来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做些什么事情,或者在等待些什么资源。</p><p><strong>下面是一个线程死锁的代码。我们下面会通过 <code>jstack</code> 命令进行死锁检查,输出死锁信息,找到发生死锁的线程。</strong></p><pre class=" language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">DeadLockDemo</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">static</span> Object resource1 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Object</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//资源 1</span> <span class="token keyword">private</span> <span class="token keyword">static</span> Object resource2 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Object</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//资源 2</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span> <span class="token keyword">synchronized</span> <span class="token punctuation">(</span>resource1<span class="token punctuation">)</span> <span class="token punctuation">{</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">"get resource1"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> Thread<span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">1000</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">InterruptedException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span> e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">"waiting get resource2"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">synchronized</span> <span class="token punctuation">(</span>resource2<span class="token punctuation">)</span> <span class="token punctuation">{</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">"get resource2"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token string">"线程 1"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span> <span class="token keyword">synchronized</span> <span class="token punctuation">(</span>resource2<span class="token punctuation">)</span> <span class="token punctuation">{</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">"get resource2"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> Thread<span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">1000</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">InterruptedException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span> e<span class="token punctuation">.</span><span class="token function">printStackTrace</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">"waiting get resource1"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">synchronized</span> <span class="token punctuation">(</span>resource1<span class="token punctuation">)</span> <span class="token punctuation">{</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>Thread<span class="token punctuation">.</span><span class="token function">currentThread</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">"get resource1"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token string">"线程 2"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">}</span></code></pre><p>Output</p><pre><code>Thread[线程 1,5,main]get resource1Thread[线程 2,5,main]get resource2Thread[线程 1,5,main]waiting get resource2Thread[线程 2,5,main]waiting get resource1</code></pre><p>线程 A 通过 synchronized (resource1) 获得 resource1 的监视器锁,然后通过<code>Thread.sleep(1000);</code>让线程 A 休眠 1s 为的是让线程 B 得到执行然后获取到 resource2 的监视器锁。线程 A 和线程 B 休眠结束了都开始企图请求获取对方的资源,然后这两个线程就会陷入互相等待的状态,这也就产生了死锁。</p><p><strong>通过 <code>jstack</code> 命令分析:</strong></p><pre class=" language-powershell"><code class="language-powershell">C:\Users\SnailClimb>jps13792 KotlinCompileDaemon7360 NettyClient2173967972 Launcher8932 Launcher9256 DeadLockDemo10764 Jps17340 NettyServerC:\Users\SnailClimb>jstack 9256</code></pre><p>输出的部分内容如下:</p><pre class=" language-powershell"><code class="language-powershell">Found one Java<span class="token operator">-</span>level deadlock:=============================<span class="token string">"线程 2"</span>: waiting to lock monitor 0x000000000333e668 <span class="token punctuation">(</span>object 0x00000000d5efe1c0<span class="token punctuation">,</span> a java<span class="token punctuation">.</span>lang<span class="token punctuation">.</span>Object<span class="token punctuation">)</span><span class="token punctuation">,</span> which is held by <span class="token string">"线程 1"</span><span class="token string">"线程 1"</span>: waiting to lock monitor 0x000000000333be88 <span class="token punctuation">(</span>object 0x00000000d5efe1d0<span class="token punctuation">,</span> a java<span class="token punctuation">.</span>lang<span class="token punctuation">.</span>Object<span class="token punctuation">)</span><span class="token punctuation">,</span> which is held by <span class="token string">"线程 2"</span>Java stack information <span class="token keyword">for</span> the threads listed above:===================================================<span class="token string">"线程 2"</span>: at DeadLockDemo<span class="token punctuation">.</span>lambda<span class="token variable">$main</span><span class="token variable">$1</span><span class="token punctuation">(</span>DeadLockDemo<span class="token punctuation">.</span>java:31<span class="token punctuation">)</span> <span class="token operator">-</span> waiting to lock &lt;0x00000000d5efe1c0> <span class="token punctuation">(</span>a java<span class="token punctuation">.</span>lang<span class="token punctuation">.</span>Object<span class="token punctuation">)</span> <span class="token operator">-</span> locked &lt;0x00000000d5efe1d0> <span class="token punctuation">(</span>a java<span class="token punctuation">.</span>lang<span class="token punctuation">.</span>Object<span class="token punctuation">)</span> at DeadLockDemo$<span class="token variable">$Lambda</span><span class="token variable">$2</span><span class="token operator">/</span>1078694789<span class="token punctuation">.</span>run<span class="token punctuation">(</span>Unknown Source<span class="token punctuation">)</span> at java<span class="token punctuation">.</span>lang<span class="token punctuation">.</span>Thread<span class="token punctuation">.</span>run<span class="token punctuation">(</span>Thread<span class="token punctuation">.</span>java:748<span class="token punctuation">)</span><span class="token string">"线程 1"</span>: at DeadLockDemo<span class="token punctuation">.</span>lambda<span class="token variable">$main</span><span class="token variable">$0</span><span class="token punctuation">(</span>DeadLockDemo<span class="token punctuation">.</span>java:16<span class="token punctuation">)</span> <span class="token operator">-</span> waiting to lock &lt;0x00000000d5efe1d0> <span class="token punctuation">(</span>a java<span class="token punctuation">.</span>lang<span class="token punctuation">.</span>Object<span class="token punctuation">)</span> <span class="token operator">-</span> locked &lt;0x00000000d5efe1c0> <span class="token punctuation">(</span>a java<span class="token punctuation">.</span>lang<span class="token punctuation">.</span>Object<span class="token punctuation">)</span> at DeadLockDemo$<span class="token variable">$Lambda</span><span class="token variable">$1</span><span class="token operator">/</span>1324119927<span class="token punctuation">.</span>run<span class="token punctuation">(</span>Unknown Source<span class="token punctuation">)</span> at java<span class="token punctuation">.</span>lang<span class="token punctuation">.</span>Thread<span class="token punctuation">.</span>run<span class="token punctuation">(</span>Thread<span class="token punctuation">.</span>java:748<span class="token punctuation">)</span>Found 1 deadlock<span class="token punctuation">.</span></code></pre><p>可以看到 <code>jstack</code> 命令已经帮我们找到发生死锁的线程的具体信息。</p><h2 id="JDK-可视化分析工具"><a href="#JDK-可视化分析工具" class="headerlink" title="JDK 可视化分析工具"></a>JDK 可视化分析工具</h2><h3 id="JConsole-Java-监视与管理控制台"><a href="#JConsole-Java-监视与管理控制台" class="headerlink" title="JConsole:Java 监视与管理控制台"></a>JConsole:Java 监视与管理控制台</h3><p>JConsole 是基于 JMX 的可视化监视、管理工具。可以很方便的监视本地及远程服务器的 java 进程的内存使用情况。你可以在控制台输出<code>console</code>命令启动或者在 JDK 目录下的 bin 目录找到<code>jconsole.exe</code>然后双击启动。</p><h4 id="连接-Jconsole"><a href="#连接-Jconsole" class="headerlink" title="连接 Jconsole"></a>连接 Jconsole</h4><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/1JConsole%E8%BF%9E%E6%8E%A5.png" alt="连接 Jconsole"></p><p>如果需要使用 JConsole 连接远程进程,可以在远程 Java 程序启动时加上下面这些参数:</p><pre class=" language-properties"><code class="language-properties"><span class="token attr-name">-Djava.rmi.server.hostname</span><span class="token punctuation">=</span><span class="token attr-value">外网访问 ip 地址 </span><span class="token attr-name">-Dcom.sun.management.jmxremote.port</span><span class="token punctuation">=</span><span class="token attr-value">60001 //监控的端口号</span><span class="token attr-name">-Dcom.sun.management.jmxremote.authenticate</span><span class="token punctuation">=</span><span class="token attr-value">false //关闭认证</span><span class="token attr-name">-Dcom.sun.management.jmxremote.ssl</span><span class="token punctuation">=</span><span class="token attr-value">false</span></code></pre><p>在使用 JConsole 连接时,远程进程地址如下:</p><pre><code>外网访问 ip 地址:60001 </code></pre><h4 id="查看-Java-程序概况"><a href="#查看-Java-程序概况" class="headerlink" title="查看 Java 程序概况"></a>查看 Java 程序概况</h4><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/2%E6%9F%A5%E7%9C%8BJava%E7%A8%8B%E5%BA%8F%E6%A6%82%E5%86%B5.png" alt="查看 Java 程序概况 "></p><h4 id="内存监控"><a href="#内存监控" class="headerlink" title="内存监控"></a>内存监控</h4><p>JConsole 可以显示当前内存的详细信息。不仅包括堆内存/非堆内存的整体信息,还可以细化到 eden 区、survivor 区等的使用情况,如下图所示。</p><p>点击右边的“执行 GC(G)”按钮可以强制应用程序执行一个 Full GC。</p><blockquote><ul><li><strong>新生代 GC(Minor GC)</strong>:指发生新生代的的垃圾收集动作,Minor GC 非常频繁,回收速度一般也比较快。</li><li><strong>老年代 GC(Major GC/Full GC)</strong>:指发生在老年代的 GC,出现了 Major GC 经常会伴随至少一次的 Minor GC(并非绝对),Major GC 的速度一般会比 Minor GC 的慢 10 倍以上。</li></ul></blockquote><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/3%E5%86%85%E5%AD%98%E7%9B%91%E6%8E%A7.png" alt="内存监控 "></p><h4 id="线程监控"><a href="#线程监控" class="headerlink" title="线程监控"></a>线程监控</h4><p>类似我们前面讲的 <code>jstack</code> 命令,不过这个是可视化的。</p><p>最下面有一个”检测死锁 (D)”按钮,点击这个按钮可以自动为你找到发生死锁的线程以及它们的详细信息 。</p><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/4%E7%BA%BF%E7%A8%8B%E7%9B%91%E6%8E%A7.png" alt="线程监控 "></p><h3 id="Visual-VM-多合一故障处理工具"><a href="#Visual-VM-多合一故障处理工具" class="headerlink" title="Visual VM:多合一故障处理工具"></a>Visual VM:多合一故障处理工具</h3><p>VisualVM 提供在 Java 虚拟机 (Java Virutal Machine, JVM) 上运行的 Java 应用程序的详细信息。在 VisualVM 的图形用户界面中,您可以方便、快捷地查看多个 Java 应用程序的相关信息。Visual VM 官网:<a href="https://visualvm.github.io/" target="_blank" rel="noopener">https://visualvm.github.io/</a> 。Visual VM 中文文档:<a href="https://visualvm.github.io/documentation.html" target="_blank" rel="noopener">https://visualvm.github.io/documentation.html</a>。</p><p>下面这段话摘自《深入理解 Java 虚拟机》。</p><blockquote><p>VisualVM(All-in-One Java Troubleshooting Tool)是到目前为止随 JDK 发布的功能最强大的运行监视和故障处理程序,官方在 VisualVM 的软件说明中写上了“All-in-One”的描述字样,预示着他除了运行监视、故障处理外,还提供了很多其他方面的功能,如性能分析(Profiling)。VisualVM 的性能分析功能甚至比起 JProfiler、YourKit 等专业且收费的 Profiling 工具都不会逊色多少,而且 VisualVM 还有一个很大的优点:不需要被监视的程序基于特殊 Agent 运行,因此他对应用程序的实际性能的影响很小,使得他可以直接应用在生产环境中。这个优点是 JProfiler、YourKit 等工具无法与之媲美的。</p></blockquote><p> VisualVM 基于 NetBeans 平台开发,因此他一开始就具备了插件扩展功能的特性,通过插件扩展支持,VisualVM 可以做到:</p><ul><li><strong>显示虚拟机进程以及进程的配置、环境信息(jps、jinfo)。</strong></li><li><strong>监视应用程序的 CPU、GC、堆、方法区以及线程的信息(jstat、jstack)。</strong></li><li><strong>dump 以及分析堆转储快照(jmap、jhat)。</strong></li><li><strong>方法级的程序运行性能分析,找到被调用最多、运行时间最长的方法。</strong></li><li><strong>离线程序快照:收集程序的运行时配置、线程 dump、内存 dump 等信息建立一个快照,可以将快照发送开发者处进行 Bug 反馈。</strong></li><li><strong>其他 plugins 的无限的可能性……</strong></li></ul><p>这里就不具体介绍 VisualVM 的使用,如果想了解的话可以看:</p><ul><li><a href="https://visualvm.github.io/documentation.html" target="_blank" rel="noopener">https://visualvm.github.io/documentation.html</a></li><li><a href="https://www.ibm.com/developerworks/cn/java/j-lo-visualvm/index.html" target="_blank" rel="noopener">https://www.ibm.com/developerworks/cn/java/j-lo-visualvm/index.html</a></li></ul><h2 id="公众号"><a href="#公众号" class="headerlink" title="公众号"></a>公众号</h2><p>如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。</p><p><strong>《Java面试突击》:</strong> 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本公众号后台回复 <strong>“Java面试突击”</strong> 即可免费领取!</p><p><strong>Java工程师必备学习资源:</strong> 一些Java工程师常用学习资源公众号后台回复关键字 <strong>“1”</strong> 即可免费无套路获取。 </p><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/167598cd2e17b8ec.png" alt="我的公众号"></p>]]></content>
<categories>
<category> JVM </category>
</categories>
<tags>
<tag> Java </tag>
<tag> JVM </tag>
</tags>
</entry>
<entry>
<title>搞定JVM面试之Java 内存区域详解</title>
<link href="/2019/08/25/java/jvm/Java%E5%86%85%E5%AD%98%E5%8C%BA%E5%9F%9F/"/>
<url>/2019/08/25/java/jvm/Java%E5%86%85%E5%AD%98%E5%8C%BA%E5%9F%9F/</url>
<content type="html"><![CDATA[<h2 id="写在前面-常见面试题"><a href="#写在前面-常见面试题" class="headerlink" title="写在前面 (常见面试题)"></a>写在前面 (常见面试题)</h2><h3 id="基本问题"><a href="#基本问题" class="headerlink" title="基本问题"></a>基本问题</h3><ul><li><strong>介绍下 Java 内存区域(运行时数据区)</strong></li><li><strong>Java 对象的创建过程(五步,建议能默写出来并且要知道每一步虚拟机做了什么)</strong></li><li><strong>对象的访问定位的两种方式(句柄和直接指针两种方式)</strong></li></ul><h3 id="拓展问题"><a href="#拓展问题" class="headerlink" title="拓展问题"></a>拓展问题</h3><ul><li><strong>String 类和常量池</strong></li><li><strong>8 种基本类型的包装类和常量池</strong></li></ul><h2 id="一-概述"><a href="#一-概述" class="headerlink" title="一 概述"></a>一 概述</h2><p>对于 Java 程序员来说,在虚拟机自动内存管理机制下,不再需要像 C/C++程序开发程序员这样为每一个 new 操作去写对应的 delete/free 操作,不容易出现内存泄漏和内存溢出问题。正是因为 Java 程序员把内存控制权利交给 Java 虚拟机,一旦出现内存泄漏和溢出方面的问题,如果不了解虚拟机是怎样使用内存的,那么排查错误将会是一个非常艰巨的任务。</p><h2 id="二-运行时数据区域"><a href="#二-运行时数据区域" class="headerlink" title="二 运行时数据区域"></a>二 运行时数据区域</h2><p>Java 虚拟机在执行 Java 程序的过程中会把它管理的内存划分成若干个不同的数据区域。JDK. 1.8 和之前的版本略有不同,下面会介绍到。</p><p><strong>JDK 1.8 之前:</strong></p><div align="center"> <img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3/JVM运行时数据区域.png" width="600px"/></div><p><strong>JDK 1.8 :</strong></p><div align="center"> <img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3Java运行时数据区域JDK1.8.png" width="600px"/></div><p><strong>线程私有的:</strong></p><ul><li>程序计数器</li><li>虚拟机栈</li><li>本地方法栈</li></ul><p><strong>线程共享的:</strong></p><ul><li>堆</li><li>方法区</li><li>直接内存 (非运行时数据区的一部分)</li></ul><h3 id="2-1-程序计数器"><a href="#2-1-程序计数器" class="headerlink" title="2.1 程序计数器"></a>2.1 程序计数器</h3><p>程序计数器是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。<strong>字节码解释器工作时通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等功能都需要依赖这个计数器来完成。</strong></p><p>另外,<strong>为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各线程之间计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。</strong></p><p><strong>从上面的介绍中我们知道程序计数器主要有两个作用:</strong></p><ol><li>字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。</li><li>在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。</li></ol><p><strong>注意:程序计数器是唯一一个不会出现 OutOfMemoryError 的内存区域,它的生命周期随着线程的创建而创建,随着线程的结束而死亡。</strong></p><h3 id="2-2-Java-虚拟机栈"><a href="#2-2-Java-虚拟机栈" class="headerlink" title="2.2 Java 虚拟机栈"></a>2.2 Java 虚拟机栈</h3><p><strong>与程序计数器一样,Java 虚拟机栈也是线程私有的,它的生命周期和线程相同,描述的是 Java 方法执行的内存模型,每次方法调用的数据都是通过栈传递的。</strong></p><p><strong>Java 内存可以粗糙的区分为堆内存(Heap)和栈内存 (Stack),其中栈就是现在说的虚拟机栈,或者说是虚拟机栈中局部变量表部分。</strong> (实际上,Java 虚拟机栈是由一个个栈帧组成,而每个栈帧中都拥有:局部变量表、操作数栈、动态链接、方法出口信息。)</p><p><strong>局部变量表主要存放了编译器可知的各种数据类型</strong>(boolean、byte、char、short、int、float、long、double)、<strong>对象引用</strong>(reference 类型,它不同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)。</p><p><strong>Java 虚拟机栈会出现两种异常:StackOverFlowError 和 OutOfMemoryError。</strong></p><ul><li><strong>StackOverFlowError:</strong> 若 Java 虚拟机栈的内存大小不允许动态扩展,那么当线程请求栈的深度超过当前 Java 虚拟机栈的最大深度的时候,就抛出 StackOverFlowError 异常。</li><li><strong>OutOfMemoryError:</strong> 若 Java 虚拟机栈的内存大小允许动态扩展,且当线程请求栈时内存用完了,无法再动态扩展了,此时抛出 OutOfMemoryError 异常。</li></ul><p>Java 虚拟机栈也是线程私有的,每个线程都有各自的 Java 虚拟机栈,而且随着线程的创建而创建,随着线程的死亡而死亡。</p><p><strong>扩展:那么方法/函数如何调用?</strong></p><p>Java 栈可用类比数据结构中栈,Java 栈中保存的主要内容是栈帧,每一次函数调用都会有一个对应的栈帧被压入 Java 栈,每一个函数调用结束后,都会有一个栈帧被弹出。</p><p>Java 方法有两种返回方式:</p><ol><li>return 语句。</li><li>抛出异常。</li></ol><p>不管哪种返回方式都会导致栈帧被弹出。</p><h3 id="2-3-本地方法栈"><a href="#2-3-本地方法栈" class="headerlink" title="2.3 本地方法栈"></a>2.3 本地方法栈</h3><p>和虚拟机栈所发挥的作用非常相似,区别是: <strong>虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。</strong> 在 HotSpot 虚拟机中和 Java 虚拟机栈合二为一。</p><p>本地方法被执行的时候,在本地方法栈也会创建一个栈帧,用于存放该本地方法的局部变量表、操作数栈、动态链接、出口信息。</p><p>方法执行完毕后相应的栈帧也会出栈并释放内存空间,也会出现 StackOverFlowError 和 OutOfMemoryError 两种异常。</p><h3 id="2-4-堆"><a href="#2-4-堆" class="headerlink" title="2.4 堆"></a>2.4 堆</h3><p>Java 虚拟机所管理的内存中最大的一块,Java 堆是所有线程共享的一块内存区域,在虚拟机启动时创建。<strong>此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。</strong></p><p>Java 堆是垃圾收集器管理的主要区域,因此也被称作<strong>GC 堆(Garbage Collected Heap)</strong>.从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法,所以 Java 堆还可以细分为:新生代和老年代:再细致一点有:Eden 空间、From Survivor、To Survivor 空间等。<strong>进一步划分的目的是更好地回收内存,或者更快地分配内存。</strong></p><p>在 JDK 7 版本及JDK 7 版本之前,堆内存被通常被分为下面三部分:</p><ol><li>新生代内存(Young Ceneration)</li><li>老生代(Old Generation)</li><li>永生代(Permanent Generation)</li></ol><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-11/JVM%E5%A0%86%E5%86%85%E5%AD%98%E7%BB%93%E6%9E%84-JDK7.jpg" alt="JVM堆内存结构-JDK7"></p><p>JDK 8 版本之后方法区(HotSpot 的永久代)被彻底移除了(JDK1.7 就已经开始了),取而代之是元空间,元空间使用的是直接内存。</p><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-11/JVM%E5%A0%86%E5%86%85%E5%AD%98%E7%BB%93%E6%9E%84-jdk8.jpg" alt="JVM堆内存结构-JDK8"></p><p><strong>上图所示的 Eden 区、两个 Survivor 区都属于新生代(为了区分,这两个 Survivor 区域按照顺序被命名为 from 和 to),中间一层属于老年代。</strong></p><p>大部分情况,对象都会首先在 Eden 区域分配,在一次新生代垃圾回收后,如果对象还存活,则会进入 s0 或者 s1,并且对象的年龄还会加 1(Eden 区-&gt;Survivor 区后对象的初始年龄变为 1),当它的年龄增加到一定程度(默认为 15 岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 <code>-XX:MaxTenuringThreshold</code> 来设置。</p><blockquote><p>修正(<a href="https://github.com/Snailclimb/JavaGuide/issues/552" target="_blank" rel="noopener">issue552</a>):“Hotspot遍历所有对象时,按照年龄从小到大对其所占用的大小进行累积,当累积的某个年龄大小超过了survivor区的一半时,取这个年龄和MaxTenuringThreshold中更小的一个值,作为新的晋升年龄阈值”。</p><p><strong>动态年龄计算的代码如下</strong></p><pre class=" language-c++"><code class="language-c++">uint ageTable::compute_tenuring_threshold(size_t survivor_capacity) { //survivor_capacity是survivor空间的大小 size_t desired_survivor_size = (size_t)((((double) survivor_capacity)*TargetSurvivorRatio)/100); size_t total = 0; uint age = 1; while (age < table_size) { total += sizes[age];//sizes数组是每个年龄段对象大小 if (total > desired_survivor_size) break; age++; } uint result = age < MaxTenuringThreshold ? age : MaxTenuringThreshold; ...}</code></pre></blockquote><p>堆这里最容易出现的就是 OutOfMemoryError 异常,并且出现这种异常之后的表现形式还会有几种,比如:</p><ol><li><strong><code>OutOfMemoryError: GC Overhead Limit Exceeded</code></strong> : 当JVM花太多时间执行垃圾回收并且只能回收很少的堆空间时,就会发生此错误。</li><li><strong><code>java.lang.OutOfMemoryError: Java heap space</code></strong> :假如在创建新的对象时, 堆内存中的空间不足以存放新创建的对象, 就会引发<code>java.lang.OutOfMemoryError: Java heap space</code> 错误。(和本机物理内存无关,和你配置的对内存大小有关!)</li><li>……</li></ol><h3 id="2-5-方法区"><a href="#2-5-方法区" class="headerlink" title="2.5 方法区"></a>2.5 方法区</h3><p>方法区与 Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然 <strong>Java 虚拟机规范把方法区描述为堆的一个逻辑部分</strong>,但是它却有一个别名叫做 <strong>Non-Heap(非堆)</strong>,目的应该是与 Java 堆区分开来。</p><p>方法区也被称为永久代。很多人都会分不清方法区和永久代的关系,为此我也查阅了文献。</p><h4 id="2-5-1-方法区和永久代的关系"><a href="#2-5-1-方法区和永久代的关系" class="headerlink" title="2.5.1 方法区和永久代的关系"></a>2.5.1 方法区和永久代的关系</h4><blockquote><p>《Java 虚拟机规范》只是规定了有方法区这么个概念和它的作用,并没有规定如何去实现它。那么,在不同的 JVM 上方法区的实现肯定是不同的了。 <strong>方法区和永久代的关系很像 Java 中接口和类的关系,类实现了接口,而永久代就是 HotSpot 虚拟机对虚拟机规范中方法区的一种实现方式。</strong> 也就是说,永久代是 HotSpot 的概念,方法区是 Java 虚拟机规范中的定义,是一种规范,而永久代是一种实现,一个是标准一个是实现,其他的虚拟机实现并没有永久代这一说法。</p></blockquote><h4 id="2-5-2-常用参数"><a href="#2-5-2-常用参数" class="headerlink" title="2.5.2 常用参数"></a>2.5.2 常用参数</h4><p>JDK 1.8 之前永久代还没被彻底移除的时候通常通过下面这些参数来调节方法区大小</p><pre class=" language-java"><code class="language-java"><span class="token operator">-</span>XX<span class="token operator">:</span>PermSize<span class="token operator">=</span>N <span class="token comment" spellcheck="true">//方法区 (永久代) 初始大小</span><span class="token operator">-</span>XX<span class="token operator">:</span>MaxPermSize<span class="token operator">=</span>N <span class="token comment" spellcheck="true">//方法区 (永久代) 最大大小,超过这个值将会抛出 OutOfMemoryError 异常:java.lang.OutOfMemoryError: PermGen</span></code></pre><p>相对而言,垃圾收集行为在这个区域是比较少出现的,但并非数据进入方法区后就“永久存在”了。</p><p>JDK 1.8 的时候,方法区(HotSpot 的永久代)被彻底移除了(JDK1.7 就已经开始了),取而代之是元空间,元空间使用的是直接内存。</p><p>下面是一些常用参数:</p><pre class=" language-java"><code class="language-java"><span class="token operator">-</span>XX<span class="token operator">:</span>MetaspaceSize<span class="token operator">=</span>N <span class="token comment" spellcheck="true">//设置 Metaspace 的初始(和最小大小)</span><span class="token operator">-</span>XX<span class="token operator">:</span>MaxMetaspaceSize<span class="token operator">=</span>N <span class="token comment" spellcheck="true">//设置 Metaspace 的最大大小</span></code></pre><p>与永久代很大的不同就是,如果不指定大小的话,随着更多类的创建,虚拟机会耗尽所有可用的系统内存。</p><h4 id="2-5-3-为什么要将永久代-PermGen-替换为元空间-MetaSpace-呢"><a href="#2-5-3-为什么要将永久代-PermGen-替换为元空间-MetaSpace-呢" class="headerlink" title="2.5.3 为什么要将永久代 (PermGen) 替换为元空间 (MetaSpace) 呢?"></a>2.5.3 为什么要将永久代 (PermGen) 替换为元空间 (MetaSpace) 呢?</h4><p>整个永久代有一个 JVM 本身设置固定大小上限,无法进行调整,而元空间使用的是直接内存,受本机可用内存的限制,并且永远不会得到 <code>java.lang.OutOfMemoryError</code>。你可以使用 <code>-XX:MaxMetaspaceSize</code> 标志设置最大元空间大小,默认值为 unlimited,这意味着它只受系统内存的限制。<code>-XX:MetaspaceSize</code> 调整标志定义元空间的初始大小如果未指定此标志,则 Metaspace 将根据运行时的应用程序需求动态地重新调整大小。</p><p>当然这只是其中一个原因,还有很多底层的原因,这里就不提了。</p><h3 id="2-6-运行时常量池"><a href="#2-6-运行时常量池" class="headerlink" title="2.6 运行时常量池"></a>2.6 运行时常量池</h3><p>运行时常量池是方法区的一部分。Class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有常量池信息(用于存放编译期生成的各种字面量和符号引用)</p><p>既然运行时常量池是方法区的一部分,自然受到方法区内存的限制,当常量池无法再申请到内存时会抛出 OutOfMemoryError 异常。</p><p><strong>JDK1.7 及之后版本的 JVM 已经将运行时常量池从方法区中移了出来,在 Java 堆(Heap)中开辟了一块区域存放运行时常量池。</strong> </p><p><img src="http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-14/26038433.jpg" alt=""><br>——图片来源:<a href="https://blog.csdn.net/wangbiao007/article/details/78545189" target="_blank" rel="noopener">https://blog.csdn.net/wangbiao007/article/details/78545189</a></p><h3 id="2-7-直接内存"><a href="#2-7-直接内存" class="headerlink" title="2.7 直接内存"></a>2.7 直接内存</h3><p><strong>直接内存并不是虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使用。而且也可能导致 OutOfMemoryError 异常出现。</strong></p><p>JDK1.4 中新加入的 <strong>NIO(New Input/Output) 类</strong>,引入了一种基于<strong>通道(Channel)</strong> 与<strong>缓存区(Buffer)</strong> 的 I/O 方式,它可以直接使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样就能在一些场景中显著提高性能,因为<strong>避免了在 Java 堆和 Native 堆之间来回复制数据</strong>。</p><p>本机直接内存的分配不会受到 Java 堆的限制,但是,既然是内存就会受到本机总内存大小以及处理器寻址空间的限制。</p><h2 id="三-HotSpot-虚拟机对象探秘"><a href="#三-HotSpot-虚拟机对象探秘" class="headerlink" title="三 HotSpot 虚拟机对象探秘"></a>三 HotSpot 虚拟机对象探秘</h2><p>通过上面的介绍我们大概知道了虚拟机的内存情况,下面我们来详细的了解一下 HotSpot 虚拟机在 Java 堆中对象分配、布局和访问的全过程。</p><h3 id="3-1-对象的创建"><a href="#3-1-对象的创建" class="headerlink" title="3.1 对象的创建"></a>3.1 对象的创建</h3><p>下图便是 Java 对象的创建过程,我建议最好是能默写出来,并且要掌握每一步在做什么。<br><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/Java%E5%88%9B%E5%BB%BA%E5%AF%B9%E8%B1%A1%E7%9A%84%E8%BF%87%E7%A8%8B.png" alt="Java创建对象的过程"></p><h4 id="Step1-类加载检查"><a href="#Step1-类加载检查" class="headerlink" title="Step1:类加载检查"></a>Step1:类加载检查</h4><p> 虚拟机遇到一条 new 指令时,首先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。如果没有,那必须先执行相应的类加载过程。</p><h4 id="Step2-分配内存"><a href="#Step2-分配内存" class="headerlink" title="Step2:分配内存"></a>Step2:分配内存</h4><p>在<strong>类加载检查</strong>通过后,接下来虚拟机将为新生对象<strong>分配内存</strong>。对象所需的内存大小在类加载完成后便可确定,为对象分配空间的任务等同于把一块确定大小的内存从 Java 堆中划分出来。<strong>分配方式</strong>有 <strong>“指针碰撞”</strong> 和 <strong>“空闲列表”</strong> 两种,<strong>选择那种分配方式由 Java 堆是否规整决定,而 Java 堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定</strong>。</p><p><strong>内存分配的两种方式:(补充内容,需要掌握)</strong></p><p>选择以上两种方式中的哪一种,取决于 Java 堆内存是否规整。而 Java 堆内存是否规整,取决于 GC 收集器的算法是”标记-清除”,还是”标记-整理”(也称作”标记-压缩”),值得注意的是,复制算法内存也是规整的</p><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/%E5%86%85%E5%AD%98%E5%88%86%E9%85%8D%E7%9A%84%E4%B8%A4%E7%A7%8D%E6%96%B9%E5%BC%8F.png" alt="内存分配的两种方式"></p><p><strong>内存分配并发问题(补充内容,需要掌握)</strong></p><p>在创建对象的时候有一个很重要的问题,就是线程安全,因为在实际开发过程中,创建对象是很频繁的事情,作为虚拟机来说,必须要保证线程是安全的,通常来讲,虚拟机采用两种方式来保证线程安全:</p><ul><li><strong>CAS+失败重试:</strong> CAS 是乐观锁的一种实现方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。<strong>虚拟机采用 CAS 配上失败重试的方式保证更新操作的原子性。</strong></li><li><strong>TLAB:</strong> 为每一个线程预先在 Eden 区分配一块儿内存,JVM 在给线程中的对象分配内存时,首先在 TLAB 分配,当对象大于 TLAB 中的剩余内存或 TLAB 的内存已用尽时,再采用上述的 CAS 进行内存分配</li></ul><h4 id="Step3-初始化零值"><a href="#Step3-初始化零值" class="headerlink" title="Step3:初始化零值"></a>Step3:初始化零值</h4><p>内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),这一步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。</p><h4 id="Step4-设置对象头"><a href="#Step4-设置对象头" class="headerlink" title="Step4:设置对象头"></a>Step4:设置对象头</h4><p>初始化零值完成之后,<strong>虚拟机要对对象进行必要的设置</strong>,例如这个对象是那个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息。 <strong>这些信息存放在对象头中。</strong> 另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。</p><h4 id="Step5-执行-init-方法"><a href="#Step5-执行-init-方法" class="headerlink" title="Step5:执行 init 方法"></a>Step5:执行 init 方法</h4><p> 在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从 Java 程序的视角来看,对象创建才刚开始,<code>&lt;init&gt;</code> 方法还没有执行,所有的字段都还为零。所以一般来说,执行 new 指令之后会接着执行 <code>&lt;init&gt;</code> 方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。</p><h3 id="3-2-对象的内存布局"><a href="#3-2-对象的内存布局" class="headerlink" title="3.2 对象的内存布局"></a>3.2 对象的内存布局</h3><p>在 Hotspot 虚拟机中,对象在内存中的布局可以分为 3 块区域:<strong>对象头</strong>、<strong>实例数据</strong>和<strong>对齐填充</strong>。</p><p><strong>Hotspot 虚拟机的对象头包括两部分信息</strong>,<strong>第一部分用于存储对象自身的自身运行时数据</strong>(哈希码、GC 分代年龄、锁状态标志等等),<strong>另一部分是类型指针</strong>,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是那个类的实例。</p><p><strong>实例数据部分是对象真正存储的有效信息</strong>,也是在程序中所定义的各种类型的字段内容。</p><p><strong>对齐填充部分不是必然存在的,也没有什么特别的含义,仅仅起占位作用。</strong> 因为 Hotspot 虚拟机的自动内存管理系统要求对象起始地址必须是 8 字节的整数倍,换句话说就是对象的大小必须是 8 字节的整数倍。而对象头部分正好是 8 字节的倍数(1 倍或 2 倍),因此,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。</p><h3 id="3-3-对象的访问定位"><a href="#3-3-对象的访问定位" class="headerlink" title="3.3 对象的访问定位"></a>3.3 对象的访问定位</h3><p>建立对象就是为了使用对象,我们的 Java 程序通过栈上的 reference 数据来操作堆上的具体对象。对象的访问方式由虚拟机实现而定,目前主流的访问方式有<strong>①使用句柄</strong>和<strong>②直接指针</strong>两种:</p><ol><li><p><strong>句柄:</strong> 如果使用句柄的话,那么 Java 堆中将会划分出一块内存来作为句柄池,reference 中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息;</p><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/%E5%AF%B9%E8%B1%A1%E7%9A%84%E8%AE%BF%E9%97%AE%E5%AE%9A%E4%BD%8D-%E4%BD%BF%E7%94%A8%E5%8F%A5%E6%9F%84.png" alt="对象的访问定位-使用句柄"></p></li><li><p><strong>直接指针:</strong> 如果使用直接指针访问,那么 Java 堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,而 reference 中存储的直接就是对象的地址。</p></li></ol><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/%E5%AF%B9%E8%B1%A1%E7%9A%84%E8%AE%BF%E9%97%AE%E5%AE%9A%E4%BD%8D-%E7%9B%B4%E6%8E%A5%E6%8C%87%E9%92%88.png" alt="对象的访问定位-直接指针"></p><p><strong>这两种对象访问方式各有优势。使用句柄来访问的最大好处是 reference 中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而 reference 本身不需要修改。使用直接指针访问方式最大的好处就是速度快,它节省了一次指针定位的时间开销。</strong></p><h2 id="四-重点补充内容"><a href="#四-重点补充内容" class="headerlink" title="四 重点补充内容"></a>四 重点补充内容</h2><h3 id="4-1-String-类和常量池"><a href="#4-1-String-类和常量池" class="headerlink" title="4.1 String 类和常量池"></a>4.1 String 类和常量池</h3><p><strong>String 对象的两种创建方式:</strong></p><pre class=" language-java"><code class="language-java">String str1 <span class="token operator">=</span> <span class="token string">"abcd"</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//先检查字符串常量池中有没有"abcd",如果字符串常量池中没有,则创建一个,然后 str1 指向字符串常量池中的对象,如果有,则直接将 str1 指向"abcd"";</span>String str2 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">String</span><span class="token punctuation">(</span><span class="token string">"abcd"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//堆中创建一个新的对象</span>String str3 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">String</span><span class="token punctuation">(</span><span class="token string">"abcd"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//堆中创建一个新的对象</span>System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>str1<span class="token operator">==</span>str2<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//false</span>System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>str2<span class="token operator">==</span>str3<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//false</span></code></pre><p>这两种不同的创建方法是有差别的。</p><ul><li>第一种方式是在常量池中拿对象;</li><li>第二种方式是直接在堆内存空间创建一个新的对象。</li></ul><p>记住一点:<strong>只要使用 new 方法,便需要创建新的对象。</strong></p><p>再给大家一个图应该更容易理解,图片来源:<a href="https://www.journaldev.com/797/what-is-java-string-pool" target="_blank" rel="noopener">https://www.journaldev.com/797/what-is-java-string-pool</a>:</p><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3String-Pool-Java1-450x249.png" alt="String-Pool-Java"></p><p><strong>String 类型的常量池比较特殊。它的主要使用方法有两种:</strong></p><ul><li>直接使用双引号声明出来的 String 对象会直接存储在常量池中。</li><li>如果不是用双引号声明的 String 对象,可以使用 String 提供的 intern 方法。String.intern() 是一个 Native 方法,它的作用是:如果运行时常量池中已经包含一个等于此 String 对象内容的字符串,则返回常量池中该字符串的引用;如果没有,JDK1.7之前(不包含1.7)的处理方式是在常量池中创建与此 String 内容相同的字符串,并返回常量池中创建的字符串的引用,JDK1.7以及之后的处理方式是在常量池中记录此字符串的引用,并返回该引用。</li></ul><pre class=" language-java"><code class="language-java"> String s1 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">String</span><span class="token punctuation">(</span><span class="token string">"计算机"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> String s2 <span class="token operator">=</span> s1<span class="token punctuation">.</span><span class="token function">intern</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> String s3 <span class="token operator">=</span> <span class="token string">"计算机"</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>s2<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//计算机</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>s1 <span class="token operator">==</span> s2<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//false,因为一个是堆内存中的 String 对象一个是常量池中的 String 对象,</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>s3 <span class="token operator">==</span> s2<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//true,因为两个都是常量池中的 String 对象</span></code></pre><p><strong>字符串拼接:</strong></p><pre class=" language-java"><code class="language-java"> String str1 <span class="token operator">=</span> <span class="token string">"str"</span><span class="token punctuation">;</span> String str2 <span class="token operator">=</span> <span class="token string">"ing"</span><span class="token punctuation">;</span> String str3 <span class="token operator">=</span> <span class="token string">"str"</span> <span class="token operator">+</span> <span class="token string">"ing"</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//常量池中的对象</span> String str4 <span class="token operator">=</span> str1 <span class="token operator">+</span> str2<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//在堆上创建的新的对象 </span> String str5 <span class="token operator">=</span> <span class="token string">"string"</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//常量池中的对象</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>str3 <span class="token operator">==</span> str4<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//false</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>str3 <span class="token operator">==</span> str5<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//true</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>str4 <span class="token operator">==</span> str5<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//false</span></code></pre><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-7/%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%8B%BC%E6%8E%A5-%E5%B8%B8%E9%87%8F%E6%B1%A02.png" alt="字符串拼接"></p><p>尽量避免多个字符串拼接,因为这样会重新创建对象。如果需要改变字符串的话,可以使用 StringBuilder 或者 StringBuffer。</p><h3 id="4-2-String-s1-new-String-“abc”-这句话创建了几个字符串对象?"><a href="#4-2-String-s1-new-String-“abc”-这句话创建了几个字符串对象?" class="headerlink" title="4.2 String s1 = new String(“abc”);这句话创建了几个字符串对象?"></a>4.2 String s1 = new String(“abc”);这句话创建了几个字符串对象?</h3><p><strong>将创建 1 或 2 个字符串。如果池中已存在字符串常量“abc”,则只会在堆空间创建一个字符串常量“abc”。如果池中没有字符串常量“abc”,那么它将首先在池中创建,然后在堆空间中创建,因此将创建总共 2 个字符串对象。</strong></p><p><strong>验证:</strong></p><pre class=" language-java"><code class="language-java"> String s1 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">String</span><span class="token punctuation">(</span><span class="token string">"abc"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 堆内存的地址值</span> String s2 <span class="token operator">=</span> <span class="token string">"abc"</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>s1 <span class="token operator">==</span> s2<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 输出 false,因为一个是堆内存,一个是常量池的内存,故两者是不同的。</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>s1<span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span>s2<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 输出 true</span></code></pre><p><strong>结果:</strong></p><pre><code>falsetrue</code></pre><h3 id="4-3-8-种基本类型的包装类和常量池"><a href="#4-3-8-种基本类型的包装类和常量池" class="headerlink" title="4.3 8 种基本类型的包装类和常量池"></a>4.3 8 种基本类型的包装类和常量池</h3><ul><li><strong>Java 基本类型的包装类的大部分都实现了常量池技术,即 Byte,Short,Integer,Long,Character,Boolean;这 5 种包装类默认创建了数值[-128,127] 的相应类型的缓存数据,但是超出此范围仍然会去创建新的对象。</strong> 为啥把缓存设置为[-128,127]区间?(<a href="https://github.com/Snailclimb/JavaGuide/issues/461" target="_blank" rel="noopener">参见issue/461</a>)性能和资源之间的权衡。</li><li><strong>两种浮点数类型的包装类 Float,Double 并没有实现常量池技术。</strong></li></ul><pre class=" language-java"><code class="language-java"> Integer i1 <span class="token operator">=</span> <span class="token number">33</span><span class="token punctuation">;</span> Integer i2 <span class="token operator">=</span> <span class="token number">33</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>i1 <span class="token operator">==</span> i2<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 输出 true</span> Integer i11 <span class="token operator">=</span> <span class="token number">333</span><span class="token punctuation">;</span> Integer i22 <span class="token operator">=</span> <span class="token number">333</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>i11 <span class="token operator">==</span> i22<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 输出 false</span> Double i3 <span class="token operator">=</span> <span class="token number">1.2</span><span class="token punctuation">;</span> Double i4 <span class="token operator">=</span> <span class="token number">1.2</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>i3 <span class="token operator">==</span> i4<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 输出 false</span></code></pre><p><strong>Integer 缓存源代码:</strong> </p><pre class=" language-java"><code class="language-java"><span class="token comment" spellcheck="true">/***此方法将始终缓存-128 到 127(包括端点)范围内的值,并可以缓存此范围之外的其他值。*/</span> <span class="token keyword">public</span> <span class="token keyword">static</span> Integer <span class="token function">valueOf</span><span class="token punctuation">(</span><span class="token keyword">int</span> i<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>i <span class="token operator">>=</span> IntegerCache<span class="token punctuation">.</span>low <span class="token operator">&amp;&amp;</span> i <span class="token operator">&lt;=</span> IntegerCache<span class="token punctuation">.</span>high<span class="token punctuation">)</span> <span class="token keyword">return</span> IntegerCache<span class="token punctuation">.</span>cache<span class="token punctuation">[</span>i <span class="token operator">+</span> <span class="token punctuation">(</span><span class="token operator">-</span>IntegerCache<span class="token punctuation">.</span>low<span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">Integer</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre><p><strong>应用场景:</strong></p><ol><li>Integer i1=40;Java 在编译的时候会直接将代码封装成 Integer i1=Integer.valueOf(40);,从而使用常量池中的对象。</li><li>Integer i1 = new Integer(40);这种情况下会创建新的对象。</li></ol><pre class=" language-java"><code class="language-java"> Integer i1 <span class="token operator">=</span> <span class="token number">40</span><span class="token punctuation">;</span> Integer i2 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Integer</span><span class="token punctuation">(</span><span class="token number">40</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>i1<span class="token operator">==</span>i2<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//输出 false</span></code></pre><p><strong>Integer 比较更丰富的一个例子:</strong></p><pre class=" language-java"><code class="language-java"> Integer i1 <span class="token operator">=</span> <span class="token number">40</span><span class="token punctuation">;</span> Integer i2 <span class="token operator">=</span> <span class="token number">40</span><span class="token punctuation">;</span> Integer i3 <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> Integer i4 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Integer</span><span class="token punctuation">(</span><span class="token number">40</span><span class="token punctuation">)</span><span class="token punctuation">;</span> Integer i5 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Integer</span><span class="token punctuation">(</span><span class="token number">40</span><span class="token punctuation">)</span><span class="token punctuation">;</span> Integer i6 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Integer</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"i1=i2 "</span> <span class="token operator">+</span> <span class="token punctuation">(</span>i1 <span class="token operator">==</span> i2<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"i1=i2+i3 "</span> <span class="token operator">+</span> <span class="token punctuation">(</span>i1 <span class="token operator">==</span> i2 <span class="token operator">+</span> i3<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"i1=i4 "</span> <span class="token operator">+</span> <span class="token punctuation">(</span>i1 <span class="token operator">==</span> i4<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"i4=i5 "</span> <span class="token operator">+</span> <span class="token punctuation">(</span>i4 <span class="token operator">==</span> i5<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"i4=i5+i6 "</span> <span class="token operator">+</span> <span class="token punctuation">(</span>i4 <span class="token operator">==</span> i5 <span class="token operator">+</span> i6<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"40=i5+i6 "</span> <span class="token operator">+</span> <span class="token punctuation">(</span><span class="token number">40</span> <span class="token operator">==</span> i5 <span class="token operator">+</span> i6<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> </code></pre><p>结果:</p><pre><code>i1=i2 truei1=i2+i3 truei1=i4 falsei4=i5 falsei4=i5+i6 true40=i5+i6 true</code></pre><p>解释:</p><p>语句 i4 == i5 + i6,因为+这个操作符不适用于 Integer 对象,首先 i5 和 i6 进行自动拆箱操作,进行数值相加,即 i4 == 40。然后 Integer 对象无法与数值进行直接比较,所以 i4 自动拆箱转为 int 值 40,最终这条语句转为 40 == 40 进行数值比较。</p><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li>《深入理解 Java 虚拟机:JVM 高级特性与最佳实践(第二版》</li><li>《实战 java 虚拟机》</li><li><a href="https://docs.oracle.com/javase/specs/index.html" target="_blank" rel="noopener">https://docs.oracle.com/javase/specs/index.html</a></li><li><a href="http://www.pointsoftware.ch/en/under-the-hood-runtime-data-areas-javas-memory-model/" target="_blank" rel="noopener">http://www.pointsoftware.ch/en/under-the-hood-runtime-data-areas-javas-memory-model/</a></li><li><a href="https://dzone.com/articles/jvm-permgen-%E2%80%93-where-art-thou" target="_blank" rel="noopener">https://dzone.com/articles/jvm-permgen-%E2%80%93-where-art-thou</a></li><li><a href="https://stackoverflow.com/questions/9095748/method-area-and-permgen" target="_blank" rel="noopener">https://stackoverflow.com/questions/9095748/method-area-and-permgen</a></li><li>深入解析String#intern<a href="https://tech.meituan.com/2014/03/06/in-depth-understanding-string-intern.html" target="_blank" rel="noopener">https://tech.meituan.com/2014/03/06/in-depth-understanding-string-intern.html</a></li></ul><h2 id="公众号"><a href="#公众号" class="headerlink" title="公众号"></a>公众号</h2><p>如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。</p><p><strong>《Java面试突击》:</strong> 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本<a href="#公众号">公众号</a>后台回复 <strong>“Java面试突击”</strong> 即可免费领取!</p><p><strong>Java工程师必备学习资源:</strong> 一些Java工程师常用学习资源<a href="#公众号">公众号</a>后台回复关键字 <strong>“1”</strong> 即可免费无套路获取。 </p><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/167598cd2e17b8ec.png" alt="我的公众号"></p>]]></content>
<categories>
<category> JVM </category>
</categories>
<tags>
<tag> Java </tag>
<tag> JVM </tag>
</tags>
</entry>
<entry>
<title>Collections 工具类和 Arrays 工具类常见方法</title>
<link href="/2019/08/22/java/java%E5%9F%BA%E7%A1%80/Collections%E5%B7%A5%E5%85%B7%E7%B1%BB%E5%92%8CArrays%E5%B7%A5%E5%85%B7%E7%B1%BB%E5%B8%B8%E8%A7%81%E6%96%B9%E6%B3%95%20/"/>
<url>/2019/08/22/java/java%E5%9F%BA%E7%A1%80/Collections%E5%B7%A5%E5%85%B7%E7%B1%BB%E5%92%8CArrays%E5%B7%A5%E5%85%B7%E7%B1%BB%E5%B8%B8%E8%A7%81%E6%96%B9%E6%B3%95%20/</url>
<content type="html"><![CDATA[<h1 id="Collections-工具类和-Arrays-工具类常见方法"><a href="#Collections-工具类和-Arrays-工具类常见方法" class="headerlink" title="Collections 工具类和 Arrays 工具类常见方法"></a>Collections 工具类和 Arrays 工具类常见方法</h1><h2 id="Collections"><a href="#Collections" class="headerlink" title="Collections"></a>Collections</h2><p>Collections 工具类常用方法:</p><ol><li>排序</li><li>查找,替换操作</li><li>同步控制(不推荐,需要线程安全的集合类型时请考虑使用 JUC 包下的并发集合)</li></ol><h3 id="排序操作"><a href="#排序操作" class="headerlink" title="排序操作"></a>排序操作</h3><pre class=" language-java"><code class="language-java"><span class="token keyword">void</span> <span class="token function">reverse</span><span class="token punctuation">(</span>List list<span class="token punctuation">)</span><span class="token comment" spellcheck="true">//反转</span><span class="token keyword">void</span> <span class="token function">shuffle</span><span class="token punctuation">(</span>List list<span class="token punctuation">)</span><span class="token comment" spellcheck="true">//随机排序</span><span class="token keyword">void</span> <span class="token function">sort</span><span class="token punctuation">(</span>List list<span class="token punctuation">)</span><span class="token comment" spellcheck="true">//按自然排序的升序排序</span><span class="token keyword">void</span> <span class="token function">sort</span><span class="token punctuation">(</span>List list<span class="token punctuation">,</span> Comparator c<span class="token punctuation">)</span><span class="token comment" spellcheck="true">//定制排序,由Comparator控制排序逻辑</span><span class="token keyword">void</span> <span class="token function">swap</span><span class="token punctuation">(</span>List list<span class="token punctuation">,</span> <span class="token keyword">int</span> i <span class="token punctuation">,</span> <span class="token keyword">int</span> j<span class="token punctuation">)</span><span class="token comment" spellcheck="true">//交换两个索引位置的元素</span><span class="token keyword">void</span> <span class="token function">rotate</span><span class="token punctuation">(</span>List list<span class="token punctuation">,</span> <span class="token keyword">int</span> distance<span class="token punctuation">)</span><span class="token comment" spellcheck="true">//旋转。当distance为正数时,将list后distance个元素整体移到前面。当distance为负数时,将 list的前distance个元素整体移到后面。</span></code></pre><p><strong>示例代码:</strong></p><pre class=" language-java"><code class="language-java"> ArrayList<span class="token operator">&lt;</span>Integer<span class="token operator">></span> arrayList <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ArrayList</span><span class="token operator">&lt;</span>Integer<span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> arrayList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span> arrayList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">;</span> arrayList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">;</span> arrayList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token operator">-</span><span class="token number">5</span><span class="token punctuation">)</span><span class="token punctuation">;</span> arrayList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token number">7</span><span class="token punctuation">)</span><span class="token punctuation">;</span> arrayList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token number">4</span><span class="token punctuation">)</span><span class="token punctuation">;</span> arrayList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token operator">-</span><span class="token number">9</span><span class="token punctuation">)</span><span class="token punctuation">;</span> arrayList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token operator">-</span><span class="token number">7</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"原始数组:"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>arrayList<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// void reverse(List list):反转</span> Collections<span class="token punctuation">.</span><span class="token function">reverse</span><span class="token punctuation">(</span>arrayList<span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"Collections.reverse(arrayList):"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>arrayList<span class="token punctuation">)</span><span class="token punctuation">;</span> Collections<span class="token punctuation">.</span><span class="token function">rotate</span><span class="token punctuation">(</span>arrayList<span class="token punctuation">,</span> <span class="token number">4</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"Collections.rotate(arrayList, 4):"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>arrayList<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// void sort(List list),按自然排序的升序排序</span> Collections<span class="token punctuation">.</span><span class="token function">sort</span><span class="token punctuation">(</span>arrayList<span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"Collections.sort(arrayList):"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>arrayList<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// void shuffle(List list),随机排序</span> Collections<span class="token punctuation">.</span><span class="token function">shuffle</span><span class="token punctuation">(</span>arrayList<span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"Collections.shuffle(arrayList):"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>arrayList<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// void swap(List list, int i , int j),交换两个索引位置的元素</span> Collections<span class="token punctuation">.</span><span class="token function">swap</span><span class="token punctuation">(</span>arrayList<span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"Collections.swap(arrayList, 2, 5):"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>arrayList<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 定制排序的用法</span> Collections<span class="token punctuation">.</span><span class="token function">sort</span><span class="token punctuation">(</span>arrayList<span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name">Comparator</span><span class="token operator">&lt;</span>Integer<span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token annotation punctuation">@Override</span> <span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">compare</span><span class="token punctuation">(</span>Integer o1<span class="token punctuation">,</span> Integer o2<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> o2<span class="token punctuation">.</span><span class="token function">compareTo</span><span class="token punctuation">(</span>o1<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"定制排序后:"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>arrayList<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><h3 id="查找-替换操作"><a href="#查找-替换操作" class="headerlink" title="查找,替换操作"></a>查找,替换操作</h3><pre class=" language-java"><code class="language-java"><span class="token keyword">int</span> <span class="token function">binarySearch</span><span class="token punctuation">(</span>List list<span class="token punctuation">,</span> Object key<span class="token punctuation">)</span><span class="token comment" spellcheck="true">//对List进行二分查找,返回索引,注意List必须是有序的</span><span class="token keyword">int</span> <span class="token function">max</span><span class="token punctuation">(</span>Collection coll<span class="token punctuation">)</span><span class="token comment" spellcheck="true">//根据元素的自然顺序,返回最大的元素。 类比int min(Collection coll)</span><span class="token keyword">int</span> <span class="token function">max</span><span class="token punctuation">(</span>Collection coll<span class="token punctuation">,</span> Comparator c<span class="token punctuation">)</span><span class="token comment" spellcheck="true">//根据定制排序,返回最大元素,排序规则由Comparatator类控制。类比int min(Collection coll, Comparator c)</span><span class="token keyword">void</span> <span class="token function">fill</span><span class="token punctuation">(</span>List list<span class="token punctuation">,</span> Object obj<span class="token punctuation">)</span><span class="token comment" spellcheck="true">//用指定的元素代替指定list中的所有元素。</span><span class="token keyword">int</span> <span class="token function">frequency</span><span class="token punctuation">(</span>Collection c<span class="token punctuation">,</span> Object o<span class="token punctuation">)</span><span class="token comment" spellcheck="true">//统计元素出现次数</span><span class="token keyword">int</span> <span class="token function">indexOfSubList</span><span class="token punctuation">(</span>List list<span class="token punctuation">,</span> List target<span class="token punctuation">)</span><span class="token comment" spellcheck="true">//统计target在list中第一次出现的索引,找不到则返回-1,类比int lastIndexOfSubList(List source, list target).</span><span class="token keyword">boolean</span> <span class="token function">replaceAll</span><span class="token punctuation">(</span>List list<span class="token punctuation">,</span> Object oldVal<span class="token punctuation">,</span> Object newVal<span class="token punctuation">)</span><span class="token punctuation">,</span> 用新元素替换旧元素</code></pre><p><strong>示例代码:</strong></p><pre class=" language-java"><code class="language-java"> ArrayList<span class="token operator">&lt;</span>Integer<span class="token operator">></span> arrayList <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ArrayList</span><span class="token operator">&lt;</span>Integer<span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> arrayList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span> arrayList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">;</span> arrayList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">;</span> arrayList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token operator">-</span><span class="token number">5</span><span class="token punctuation">)</span><span class="token punctuation">;</span> arrayList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token number">7</span><span class="token punctuation">)</span><span class="token punctuation">;</span> arrayList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token number">4</span><span class="token punctuation">)</span><span class="token punctuation">;</span> arrayList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token operator">-</span><span class="token number">9</span><span class="token punctuation">)</span><span class="token punctuation">;</span> arrayList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token operator">-</span><span class="token number">7</span><span class="token punctuation">)</span><span class="token punctuation">;</span> ArrayList<span class="token operator">&lt;</span>Integer<span class="token operator">></span> arrayList2 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ArrayList</span><span class="token operator">&lt;</span>Integer<span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> arrayList2<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token operator">-</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">;</span> arrayList2<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token operator">-</span><span class="token number">5</span><span class="token punctuation">)</span><span class="token punctuation">;</span> arrayList2<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token number">7</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"原始数组:"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>arrayList<span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"Collections.max(arrayList):"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>Collections<span class="token punctuation">.</span><span class="token function">max</span><span class="token punctuation">(</span>arrayList<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"Collections.min(arrayList):"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>Collections<span class="token punctuation">.</span><span class="token function">min</span><span class="token punctuation">(</span>arrayList<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"Collections.replaceAll(arrayList, 3, -3):"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> Collections<span class="token punctuation">.</span><span class="token function">replaceAll</span><span class="token punctuation">(</span>arrayList<span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">,</span> <span class="token operator">-</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>arrayList<span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"Collections.frequency(arrayList, -3):"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>Collections<span class="token punctuation">.</span><span class="token function">frequency</span><span class="token punctuation">(</span>arrayList<span class="token punctuation">,</span> <span class="token operator">-</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"Collections.indexOfSubList(arrayList, arrayList2):"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>Collections<span class="token punctuation">.</span><span class="token function">indexOfSubList</span><span class="token punctuation">(</span>arrayList<span class="token punctuation">,</span> arrayList2<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"Collections.binarySearch(arrayList, 7):"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 对List进行二分查找,返回索引,List必须是有序的</span> Collections<span class="token punctuation">.</span><span class="token function">sort</span><span class="token punctuation">(</span>arrayList<span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>Collections<span class="token punctuation">.</span><span class="token function">binarySearch</span><span class="token punctuation">(</span>arrayList<span class="token punctuation">,</span> <span class="token number">7</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><h3 id="同步控制"><a href="#同步控制" class="headerlink" title="同步控制"></a>同步控制</h3><p>Collections提供了多个<code>synchronizedXxx()</code>方法·,该方法可以将指定集合包装成线程同步的集合,从而解决多线程并发访问集合时的线程安全问题。</p><p>我们知道 HashSet,TreeSet,ArrayList,LinkedList,HashMap,TreeMap 都是线程不安全的。Collections提供了多个静态方法可以把他们包装成线程同步的集合。</p><p><strong>最好不要用下面这些方法,效率非常低,需要线程安全的集合类型时请考虑使用 JUC 包下的并发集合。</strong></p><p>方法如下:</p><pre class=" language-java"><code class="language-java"><span class="token function">synchronizedCollection</span><span class="token punctuation">(</span>Collection<span class="token operator">&lt;</span>T<span class="token operator">></span> c<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">//返回指定 collection 支持的同步(线程安全的)collection。</span><span class="token function">synchronizedList</span><span class="token punctuation">(</span>List<span class="token operator">&lt;</span>T<span class="token operator">></span> list<span class="token punctuation">)</span><span class="token comment" spellcheck="true">//返回指定列表支持的同步(线程安全的)List。</span><span class="token function">synchronizedMap</span><span class="token punctuation">(</span>Map<span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> m<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">//返回由指定映射支持的同步(线程安全的)Map。</span><span class="token function">synchronizedSet</span><span class="token punctuation">(</span>Set<span class="token operator">&lt;</span>T<span class="token operator">></span> s<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">//返回指定 set 支持的同步(线程安全的)set。</span></code></pre><h3 id="Collections还可以设置不可变集合,提供了如下三类方法:"><a href="#Collections还可以设置不可变集合,提供了如下三类方法:" class="headerlink" title="Collections还可以设置不可变集合,提供了如下三类方法:"></a>Collections还可以设置不可变集合,提供了如下三类方法:</h3><pre class=" language-java"><code class="language-java"><span class="token function">emptyXxx</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">:</span> 返回一个空的、不可变的集合对象,此处的集合既可以是List,也可以是Set,还可以是Map。<span class="token function">singletonXxx</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">:</span> 返回一个只包含指定对象(只有一个或一个元素)的不可变的集合对象,此处的集合可以是:List,Set,Map。<span class="token function">unmodifiableXxx</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">:</span> 返回指定集合对象的不可变视图,此处的集合可以是:List,Set,Map。上面三类方法的参数是原有的集合对象,返回值是该集合的”只读“版本。</code></pre><p><strong>示例代码:</strong></p><pre class=" language-java"><code class="language-java"> ArrayList<span class="token operator">&lt;</span>Integer<span class="token operator">></span> arrayList <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ArrayList</span><span class="token operator">&lt;</span>Integer<span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> arrayList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span> arrayList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">;</span> arrayList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">;</span> arrayList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token operator">-</span><span class="token number">5</span><span class="token punctuation">)</span><span class="token punctuation">;</span> arrayList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token number">7</span><span class="token punctuation">)</span><span class="token punctuation">;</span> arrayList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token number">4</span><span class="token punctuation">)</span><span class="token punctuation">;</span> arrayList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token operator">-</span><span class="token number">9</span><span class="token punctuation">)</span><span class="token punctuation">;</span> arrayList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token operator">-</span><span class="token number">7</span><span class="token punctuation">)</span><span class="token punctuation">;</span> HashSet<span class="token operator">&lt;</span>Integer<span class="token operator">></span> integers1 <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">HashSet</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> integers1<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span> integers1<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">;</span> integers1<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span> Map scores <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">HashMap</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> scores<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token string">"语文"</span> <span class="token punctuation">,</span> <span class="token number">80</span><span class="token punctuation">)</span><span class="token punctuation">;</span> scores<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token string">"Java"</span> <span class="token punctuation">,</span> <span class="token number">82</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">//Collections.emptyXXX();创建一个空的、不可改变的XXX对象</span> List<span class="token operator">&lt;</span>Object<span class="token operator">></span> list <span class="token operator">=</span> Collections<span class="token punctuation">.</span><span class="token function">emptyList</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>list<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//[]</span> Set<span class="token operator">&lt;</span>Object<span class="token operator">></span> objects <span class="token operator">=</span> Collections<span class="token punctuation">.</span><span class="token function">emptySet</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>objects<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//[]</span> Map<span class="token operator">&lt;</span>Object<span class="token punctuation">,</span> Object<span class="token operator">></span> objectObjectMap <span class="token operator">=</span> Collections<span class="token punctuation">.</span><span class="token function">emptyMap</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>objectObjectMap<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//{}</span> <span class="token comment" spellcheck="true">//Collections.singletonXXX();</span> List<span class="token operator">&lt;</span>ArrayList<span class="token operator">&lt;</span>Integer<span class="token operator">>></span> arrayLists <span class="token operator">=</span> Collections<span class="token punctuation">.</span><span class="token function">singletonList</span><span class="token punctuation">(</span>arrayList<span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>arrayLists<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//[[-1, 3, 3, -5, 7, 4, -9, -7]]</span> <span class="token comment" spellcheck="true">//创建一个只有一个元素,且不可改变的Set对象</span> Set<span class="token operator">&lt;</span>ArrayList<span class="token operator">&lt;</span>Integer<span class="token operator">>></span> singleton <span class="token operator">=</span> Collections<span class="token punctuation">.</span><span class="token function">singleton</span><span class="token punctuation">(</span>arrayList<span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>singleton<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//[[-1, 3, 3, -5, 7, 4, -9, -7]]</span> Map<span class="token operator">&lt;</span>String<span class="token punctuation">,</span> String<span class="token operator">></span> nihao <span class="token operator">=</span> Collections<span class="token punctuation">.</span><span class="token function">singletonMap</span><span class="token punctuation">(</span><span class="token string">"1"</span><span class="token punctuation">,</span> <span class="token string">"nihao"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>nihao<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//{1=nihao}</span> <span class="token comment" spellcheck="true">//unmodifiableXXX();创建普通XXX对象对应的不可变版本</span> List<span class="token operator">&lt;</span>Integer<span class="token operator">></span> integers <span class="token operator">=</span> Collections<span class="token punctuation">.</span><span class="token function">unmodifiableList</span><span class="token punctuation">(</span>arrayList<span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>integers<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//[-1, 3, 3, -5, 7, 4, -9, -7]</span> Set<span class="token operator">&lt;</span>Integer<span class="token operator">></span> integers2 <span class="token operator">=</span> Collections<span class="token punctuation">.</span><span class="token function">unmodifiableSet</span><span class="token punctuation">(</span>integers1<span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>integers2<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//[1, 2, 3]</span> Map<span class="token operator">&lt;</span>Object<span class="token punctuation">,</span> Object<span class="token operator">></span> objectObjectMap2 <span class="token operator">=</span> Collections<span class="token punctuation">.</span><span class="token function">unmodifiableMap</span><span class="token punctuation">(</span>scores<span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>objectObjectMap2<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//{Java=82, 语文=80}</span> <span class="token comment" spellcheck="true">//添加出现异常:java.lang.UnsupportedOperationException</span><span class="token comment" spellcheck="true">// list.add(1);</span><span class="token comment" spellcheck="true">// arrayLists.add(arrayList);</span><span class="token comment" spellcheck="true">// integers.add(1);</span></code></pre><h2 id="Arrays类的常见操作"><a href="#Arrays类的常见操作" class="headerlink" title="Arrays类的常见操作"></a>Arrays类的常见操作</h2><ol><li>排序 : <code>sort()</code></li><li>查找 : <code>binarySearch()</code></li><li>比较: <code>equals()</code></li><li>填充 : <code>fill()</code></li><li>转列表: <code>asList()</code></li><li>转字符串 : <code>toString()</code></li><li>复制: <code>copyOf()</code></li></ol><h3 id="排序-sort"><a href="#排序-sort" class="headerlink" title="排序 : sort()"></a>排序 : <code>sort()</code></h3><pre class=" language-java"><code class="language-java"> <span class="token comment" spellcheck="true">// *************排序 sort****************</span> <span class="token keyword">int</span> a<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">7</span><span class="token punctuation">,</span> <span class="token number">6</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">,</span> <span class="token number">4</span><span class="token punctuation">,</span> <span class="token number">9</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// sort(int[] a)方法按照数字顺序排列指定的数组。</span> Arrays<span class="token punctuation">.</span><span class="token function">sort</span><span class="token punctuation">(</span>a<span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"Arrays.sort(a):"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">:</span> a<span class="token punctuation">)</span> <span class="token punctuation">{</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">print</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">// 换行</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// sort(int[] a,int fromIndex,int toIndex)按升序排列数组的指定范围</span> <span class="token keyword">int</span> b<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">7</span><span class="token punctuation">,</span> <span class="token number">6</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">,</span> <span class="token number">4</span><span class="token punctuation">,</span> <span class="token number">9</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> Arrays<span class="token punctuation">.</span><span class="token function">sort</span><span class="token punctuation">(</span>b<span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">6</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"Arrays.sort(b, 2, 6):"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">:</span> b<span class="token punctuation">)</span> <span class="token punctuation">{</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">print</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">// 换行</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">int</span> c<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">7</span><span class="token punctuation">,</span> <span class="token number">6</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">,</span> <span class="token number">4</span><span class="token punctuation">,</span> <span class="token number">9</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// parallelSort(int[] a) 按照数字顺序排列指定的数组(并行的)。同sort方法一样也有按范围的排序</span> Arrays<span class="token punctuation">.</span><span class="token function">parallelSort</span><span class="token punctuation">(</span>c<span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"Arrays.parallelSort(c):"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">:</span> c<span class="token punctuation">)</span> <span class="token punctuation">{</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">print</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">// 换行</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// parallelSort给字符数组排序,sort也可以</span> <span class="token keyword">char</span> d<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token string">'a'</span><span class="token punctuation">,</span> <span class="token string">'f'</span><span class="token punctuation">,</span> <span class="token string">'b'</span><span class="token punctuation">,</span> <span class="token string">'c'</span><span class="token punctuation">,</span> <span class="token string">'e'</span><span class="token punctuation">,</span> <span class="token string">'A'</span><span class="token punctuation">,</span> <span class="token string">'C'</span><span class="token punctuation">,</span> <span class="token string">'B'</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> Arrays<span class="token punctuation">.</span><span class="token function">parallelSort</span><span class="token punctuation">(</span>d<span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"Arrays.parallelSort(d):"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">char</span> d2 <span class="token operator">:</span> d<span class="token punctuation">)</span> <span class="token punctuation">{</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">print</span><span class="token punctuation">(</span>d2<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">// 换行</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><p>在做算法面试题的时候,我们还可能会经常遇到对字符串排序的情况,<code>Arrays.sort()</code> 对每个字符串的特定位置进行比较,然后按照升序排序。</p><pre class=" language-java"><code class="language-java">String<span class="token punctuation">[</span><span class="token punctuation">]</span> strs <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token string">"abcdehg"</span><span class="token punctuation">,</span> <span class="token string">"abcdefg"</span><span class="token punctuation">,</span> <span class="token string">"abcdeag"</span> <span class="token punctuation">}</span><span class="token punctuation">;</span>Arrays<span class="token punctuation">.</span><span class="token function">sort</span><span class="token punctuation">(</span>strs<span class="token punctuation">)</span><span class="token punctuation">;</span>System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>Arrays<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span>strs<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//[abcdeag, abcdefg, abcdehg]</span></code></pre><h3 id="查找-binarySearch"><a href="#查找-binarySearch" class="headerlink" title="查找 : binarySearch()"></a>查找 : <code>binarySearch()</code></h3><pre class=" language-java"><code class="language-java"> <span class="token comment" spellcheck="true">// *************查找 binarySearch()****************</span> <span class="token keyword">char</span><span class="token punctuation">[</span><span class="token punctuation">]</span> e <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token string">'a'</span><span class="token punctuation">,</span> <span class="token string">'f'</span><span class="token punctuation">,</span> <span class="token string">'b'</span><span class="token punctuation">,</span> <span class="token string">'c'</span><span class="token punctuation">,</span> <span class="token string">'e'</span><span class="token punctuation">,</span> <span class="token string">'A'</span><span class="token punctuation">,</span> <span class="token string">'C'</span><span class="token punctuation">,</span> <span class="token string">'B'</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 排序后再进行二分查找,否则找不到</span> Arrays<span class="token punctuation">.</span><span class="token function">sort</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"Arrays.sort(e)"</span> <span class="token operator">+</span> Arrays<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"Arrays.binarySearch(e, 'c'):"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">int</span> s <span class="token operator">=</span> Arrays<span class="token punctuation">.</span><span class="token function">binarySearch</span><span class="token punctuation">(</span>e<span class="token punctuation">,</span> <span class="token string">'c'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"字符c在数组的位置:"</span> <span class="token operator">+</span> s<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><h3 id="比较-equals"><a href="#比较-equals" class="headerlink" title="比较: equals()"></a>比较: <code>equals()</code></h3><pre class=" language-java"><code class="language-java"> <span class="token comment" spellcheck="true">// *************比较 equals****************</span> <span class="token keyword">char</span><span class="token punctuation">[</span><span class="token punctuation">]</span> e <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token string">'a'</span><span class="token punctuation">,</span> <span class="token string">'f'</span><span class="token punctuation">,</span> <span class="token string">'b'</span><span class="token punctuation">,</span> <span class="token string">'c'</span><span class="token punctuation">,</span> <span class="token string">'e'</span><span class="token punctuation">,</span> <span class="token string">'A'</span><span class="token punctuation">,</span> <span class="token string">'C'</span><span class="token punctuation">,</span> <span class="token string">'B'</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">char</span><span class="token punctuation">[</span><span class="token punctuation">]</span> f <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token string">'a'</span><span class="token punctuation">,</span> <span class="token string">'f'</span><span class="token punctuation">,</span> <span class="token string">'b'</span><span class="token punctuation">,</span> <span class="token string">'c'</span><span class="token punctuation">,</span> <span class="token string">'e'</span><span class="token punctuation">,</span> <span class="token string">'A'</span><span class="token punctuation">,</span> <span class="token string">'C'</span><span class="token punctuation">,</span> <span class="token string">'B'</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">/* * 元素数量相同,并且相同位置的元素相同。 另外,如果两个数组引用都是null,则它们被认为是相等的 。 */</span> <span class="token comment" spellcheck="true">// 输出true</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"Arrays.equals(e, f):"</span> <span class="token operator">+</span> Arrays<span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span>e<span class="token punctuation">,</span> f<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><h3 id="填充-fill"><a href="#填充-fill" class="headerlink" title="填充 : fill()"></a>填充 : <code>fill()</code></h3><pre class=" language-java"><code class="language-java"> <span class="token comment" spellcheck="true">// *************填充fill(批量初始化)****************</span> <span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> g <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">,</span> <span class="token number">6</span><span class="token punctuation">,</span> <span class="token number">6</span><span class="token punctuation">,</span> <span class="token number">6</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 数组中所有元素重新分配值</span> Arrays<span class="token punctuation">.</span><span class="token function">fill</span><span class="token punctuation">(</span>g<span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"Arrays.fill(g, 3):"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 输出结果:333333333</span> <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">:</span> g<span class="token punctuation">)</span> <span class="token punctuation">{</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">print</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">// 换行</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> h <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">,</span> <span class="token number">6</span><span class="token punctuation">,</span> <span class="token number">6</span><span class="token punctuation">,</span> <span class="token number">6</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 数组中指定范围元素重新分配值</span> Arrays<span class="token punctuation">.</span><span class="token function">fill</span><span class="token punctuation">(</span>h<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">9</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"Arrays.fill(h, 0, 2, 9);:"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 输出结果:993333666</span> <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">:</span> h<span class="token punctuation">)</span> <span class="token punctuation">{</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">print</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre><h3 id="转列表-asList"><a href="#转列表-asList" class="headerlink" title="转列表 asList()"></a>转列表 <code>asList()</code></h3><pre class=" language-java"><code class="language-java"> <span class="token comment" spellcheck="true">// *************转列表 asList()****************</span> <span class="token comment" spellcheck="true">/* * 返回由指定数组支持的固定大小的列表。 * (将返回的列表更改为“写入数组”。)该方法作为基于数组和基于集合的API之间的桥梁,与Collection.toArray()相结合 。 * 返回的列表是可序列化的,并实现RandomAccess 。 * 此方法还提供了一种方便的方式来创建一个初始化为包含几个元素的固定大小的列表如下: */</span> List<span class="token operator">&lt;</span>String<span class="token operator">></span> stooges <span class="token operator">=</span> Arrays<span class="token punctuation">.</span><span class="token function">asList</span><span class="token punctuation">(</span><span class="token string">"Larry"</span><span class="token punctuation">,</span> <span class="token string">"Moe"</span><span class="token punctuation">,</span> <span class="token string">"Curly"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>stooges<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><h3 id="转字符串-toString"><a href="#转字符串-toString" class="headerlink" title="转字符串 toString()"></a>转字符串 <code>toString()</code></h3><pre class=" language-java"><code class="language-java"> <span class="token comment" spellcheck="true">// *************转字符串 toString()****************</span> <span class="token comment" spellcheck="true">/* * 返回指定数组的内容的字符串表示形式。 */</span> <span class="token keyword">char</span><span class="token punctuation">[</span><span class="token punctuation">]</span> k <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token string">'a'</span><span class="token punctuation">,</span> <span class="token string">'f'</span><span class="token punctuation">,</span> <span class="token string">'b'</span><span class="token punctuation">,</span> <span class="token string">'c'</span><span class="token punctuation">,</span> <span class="token string">'e'</span><span class="token punctuation">,</span> <span class="token string">'A'</span><span class="token punctuation">,</span> <span class="token string">'C'</span><span class="token punctuation">,</span> <span class="token string">'B'</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>Arrays<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span>k<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// [a, f, b, c, e, A, C, B]</span></code></pre><h3 id="复制-copyOf"><a href="#复制-copyOf" class="headerlink" title="复制 copyOf()"></a>复制 <code>copyOf()</code></h3><pre class=" language-java"><code class="language-java"> <span class="token comment" spellcheck="true">// *************复制 copy****************</span> <span class="token comment" spellcheck="true">// copyOf 方法实现数组复制,h为数组,6为复制的长度</span> <span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> h <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">,</span> <span class="token number">6</span><span class="token punctuation">,</span> <span class="token number">6</span><span class="token punctuation">,</span> <span class="token number">6</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">int</span> i<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=</span> Arrays<span class="token punctuation">.</span><span class="token function">copyOf</span><span class="token punctuation">(</span>h<span class="token punctuation">,</span> <span class="token number">6</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"Arrays.copyOf(h, 6);:"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 输出结果:123333</span> <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> j <span class="token operator">:</span> i<span class="token punctuation">)</span> <span class="token punctuation">{</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">print</span><span class="token punctuation">(</span>j<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">// 换行</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// copyOfRange将指定数组的指定范围复制到新数组中</span> <span class="token keyword">int</span> j<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=</span> Arrays<span class="token punctuation">.</span><span class="token function">copyOfRange</span><span class="token punctuation">(</span>h<span class="token punctuation">,</span> <span class="token number">6</span><span class="token punctuation">,</span> <span class="token number">11</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"Arrays.copyOfRange(h, 6, 11):"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 输出结果66600(h数组只有9个元素这里是从索引6到索引11复制所以不足的就为0)</span> <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> j2 <span class="token operator">:</span> j<span class="token punctuation">)</span> <span class="token punctuation">{</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">print</span><span class="token punctuation">(</span>j2<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">// 换行</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>]]></content>
<categories>
<category> Java </category>
</categories>
<tags>
<tag> Java </tag>
</tags>
</entry>
<entry>
<title>全网阅读过20k的Java集合框架常见面试题总结!</title>
<link href="/2019/08/21/java/java%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6/%E5%85%A8%E7%BD%91%E9%98%85%E8%AF%BB%E8%BF%8720k%E7%9A%84Java%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6%E5%B8%B8%E8%A7%81%E9%9D%A2%E8%AF%95%E9%A2%98%E6%80%BB%E7%BB%93%EF%BC%81/"/>
<url>/2019/08/21/java/java%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6/%E5%85%A8%E7%BD%91%E9%98%85%E8%AF%BB%E8%BF%8720k%E7%9A%84Java%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6%E5%B8%B8%E8%A7%81%E9%9D%A2%E8%AF%95%E9%A2%98%E6%80%BB%E7%BB%93%EF%BC%81/</url>
<content type="html"><![CDATA[<blockquote><p>本文为 SnailClimb 的原创,目前已经收录自我开源的 <a href="https://github.com/Snailclimb/JavaGuide" target="_blank" rel="noopener">JavaGuide</a> 中(61.5 k Star!【Java学习+面试指南】 一份涵盖大部分Java程序员所需要掌握的核心知识。欢迎 Star!)。</p><p>文末有我的公众号,公众号里有我最新整理的Java学习资料,免费分享。</p><p>这么好的文章,一定好先赞后看!!!建议养成这个好习惯!!爱你们!😍</p></blockquote><h1 id="剖析面试最常见问题之Java集合框架"><a href="#剖析面试最常见问题之Java集合框架" class="headerlink" title="剖析面试最常见问题之Java集合框架"></a>剖析面试最常见问题之Java集合框架</h1><p>当了会标题党,这是第一次,后面还有很多次!不过这文章全网阅读肯定是超过 20 k 的,而且经过了很多同行的优化,质量有保障哦!</p><p><img src="http://wx2.sinaimg.cn/large/006APoFYly1g8whnrq4iwg309v05kgx6.gif" alt=""></p><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><p><strong>1. 是否保证线程安全:</strong> <code>ArrayList</code> 和 <code>LinkedList</code> 都是不同步的,也就是不保证线程安全;</p></li><li><p><strong>2. 底层数据结构:</strong> <code>Arraylist</code> 底层使用的是 <strong><code>Object</code> 数组</strong>;<code>LinkedList</code> 底层使用的是 <strong>双向链表</strong> 数据结构(JDK1.6之前为循环链表,JDK1.7取消了循环。注意双向链表和双向循环链表的区别,下面有介绍到!)</p></li><li><p><strong>3. 插入和删除是否受元素位置的影响:</strong> ① <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)个元素都要执行向后位/向前移一位的操作。 ② <strong><code>LinkedList</code> 采用链表存储,所以对于<code>add(E e)</code>方法的插入,删除元素时间复杂度不受元素位置的影响,近似 O(1),如果是要在指定位置<code>i</code>插入和删除元素的话(<code>(add(int index, E element)</code>) 时间复杂度近似为<code>o(n))</code>因为需要先移动到指定位置再插入。</strong></p></li><li><p><strong>4. 是否支持快速随机访问:</strong> <code>LinkedList</code> 不支持高效的随机元素访问,而 <code>ArrayList</code> 支持。快速随机访问就是通过元素的序号快速获取元素对象(对应于<code>get(int index)</code>方法)。</p></li><li><p><strong>5. 内存空间占用:</strong> ArrayList的空 间浪费主要体现在在list列表的结尾会预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗比ArrayList更多的空间(因为要存放直接后继和直接前驱以及数据)。</p></li></ul><h3 id="补充内容-RandomAccess接口"><a href="#补充内容-RandomAccess接口" class="headerlink" title="补充内容:RandomAccess接口"></a><strong>补充内容:RandomAccess接口</strong></h3><pre class=" language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">RandomAccess</span> <span class="token punctuation">{</span><span class="token punctuation">}</span></code></pre><p>查看源码我们发现实际上 <code>RandomAccess</code> 接口中什么都没有定义。所以,在我看来 <code>RandomAccess</code> 接口不过是一个标识罢了。标识什么? 标识实现这个接口的类具有随机访问功能。</p><p>在 <code>binarySearch(</code>)方法中,它要判断传入的list 是否 <code>RamdomAccess</code> 的实例,如果是,调用<code>indexedBinarySearch()</code>方法,如果不是,那么调用<code>iteratorBinarySearch()</code>方法</p><pre class=" language-java"><code class="language-java"> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token operator">&lt;</span>T<span class="token operator">></span> <span class="token keyword">int</span> <span class="token function">binarySearch</span><span class="token punctuation">(</span>List<span class="token operator">&lt;</span><span class="token operator">?</span> <span class="token keyword">extends</span> <span class="token class-name">Comparable</span><span class="token operator">&lt;</span><span class="token operator">?</span> <span class="token keyword">super</span> T<span class="token operator">>></span> list<span class="token punctuation">,</span> T key<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>list <span class="token keyword">instanceof</span> <span class="token class-name">RandomAccess</span> <span class="token operator">||</span> list<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">&lt;</span>BINARYSEARCH_THRESHOLD<span class="token punctuation">)</span> <span class="token keyword">return</span> Collections<span class="token punctuation">.</span><span class="token function">indexedBinarySearch</span><span class="token punctuation">(</span>list<span class="token punctuation">,</span> key<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">else</span> <span class="token keyword">return</span> Collections<span class="token punctuation">.</span><span class="token function">iteratorBinarySearch</span><span class="token punctuation">(</span>list<span class="token punctuation">,</span> key<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre><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://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/%E5%8F%8C%E5%90%91%E9%93%BE%E8%A1%A8.png" alt="双向链表"></p><p><strong>双向循环链表:</strong> 最后一个节点的 next 指向head,而 head 的prev指向最后一个节点,构成一个环。</p><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/%E5%8F%8C%E5%90%91%E5%BE%AA%E7%8E%AF%E9%93%BE%E8%A1%A8.png" alt="双向循环链表"></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><p>详见笔主的这篇文章:<a href="https://github.com/Snailclimb/JavaGuide/blob/master/docs/java/collection/ArrayList-Grow.md" target="_blank" rel="noopener">通过源码一步一步分析ArrayList 扩容机制</a></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><pre class=" language-java"><code class="language-java"> <span class="token keyword">public</span> <span class="token function">HashMap</span><span class="token punctuation">(</span><span class="token keyword">int</span> initialCapacity<span class="token punctuation">,</span> <span class="token keyword">float</span> loadFactor<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>initialCapacity <span class="token operator">&lt;</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">IllegalArgumentException</span><span class="token punctuation">(</span><span class="token string">"Illegal initial capacity: "</span> <span class="token operator">+</span> initialCapacity<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>initialCapacity <span class="token operator">></span> MAXIMUM_CAPACITY<span class="token punctuation">)</span> initialCapacity <span class="token operator">=</span> MAXIMUM_CAPACITY<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>loadFactor <span class="token operator">&lt;=</span> <span class="token number">0</span> <span class="token operator">||</span> Float<span class="token punctuation">.</span><span class="token function">isNaN</span><span class="token punctuation">(</span>loadFactor<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">IllegalArgumentException</span><span class="token punctuation">(</span><span class="token string">"Illegal load factor: "</span> <span class="token operator">+</span> loadFactor<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>loadFactor <span class="token operator">=</span> loadFactor<span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>threshold <span class="token operator">=</span> <span class="token function">tableSizeFor</span><span class="token punctuation">(</span>initialCapacity<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token function">HashMap</span><span class="token punctuation">(</span><span class="token keyword">int</span> initialCapacity<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">(</span>initialCapacity<span class="token punctuation">,</span> DEFAULT_LOAD_FACTOR<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre><p>下面这个方法保证了 HashMap 总是使用2的幂作为哈希表的大小。</p><pre class=" language-java"><code class="language-java"> <span class="token comment" spellcheck="true">/** * Returns a power of two size for the given target capacity. */</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> <span class="token function">tableSizeFor</span><span class="token punctuation">(</span><span class="token keyword">int</span> cap<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">int</span> n <span class="token operator">=</span> cap <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">;</span> n <span class="token operator">|=</span> n <span class="token operator">>>></span> <span class="token number">1</span><span class="token punctuation">;</span> n <span class="token operator">|=</span> n <span class="token operator">>>></span> <span class="token number">2</span><span class="token punctuation">;</span> n <span class="token operator">|=</span> n <span class="token operator">>>></span> <span class="token number">4</span><span class="token punctuation">;</span> n <span class="token operator">|=</span> n <span class="token operator">>>></span> <span class="token number">8</span><span class="token punctuation">;</span> n <span class="token operator">|=</span> n <span class="token operator">>>></span> <span class="token number">16</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">(</span>n <span class="token operator">&lt;</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token number">1</span> <span class="token operator">:</span> <span class="token punctuation">(</span>n <span class="token operator">>=</span> MAXIMUM_CAPACITY<span class="token punctuation">)</span> <span class="token operator">?</span> MAXIMUM_CAPACITY <span class="token operator">:</span> n <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre><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就不会让加入操作成功。(摘自我的Java启蒙书《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>==与equals的区别</strong></p><ol><li>==是判断两个变量或实例是不是指向同一个内存空间 equals是判断两个变量或实例所指向的内存空间的值是不是相同</li><li>==是指对内存地址进行比较 equals()是对字符串的内容进行比较</li><li>==指引用是否相同 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><pre class=" language-java"><code class="language-java"> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token keyword">int</span> <span class="token function">hash</span><span class="token punctuation">(</span>Object key<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">int</span> h<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// key.hashCode():返回散列值也就是hashcode</span> <span class="token comment" spellcheck="true">// ^ :按位异或</span> <span class="token comment" spellcheck="true">// >>>:无符号右移,忽略符号位,空位都以0补齐</span> <span class="token keyword">return</span> <span class="token punctuation">(</span>key <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token number">0</span> <span class="token operator">:</span> <span class="token punctuation">(</span>h <span class="token operator">=</span> key<span class="token punctuation">.</span><span class="token function">hashCode</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">^</span> <span class="token punctuation">(</span>h <span class="token operator">>>></span> <span class="token number">16</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre><p>对比一下 JDK1.7的 HashMap 的 hash 方法源码.</p><pre class=" language-java"><code class="language-java"><span class="token keyword">static</span> <span class="token keyword">int</span> <span class="token function">hash</span><span class="token punctuation">(</span><span class="token keyword">int</span> h<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// This function ensures that hashCodes that differ only by</span> <span class="token comment" spellcheck="true">// constant multiples at each bit position have a bounded</span> <span class="token comment" spellcheck="true">// number of collisions (approximately 8 at default load factor).</span> h <span class="token operator">^=</span> <span class="token punctuation">(</span>h <span class="token operator">>>></span> <span class="token number">20</span><span class="token punctuation">)</span> <span class="token operator">^</span> <span class="token punctuation">(</span>h <span class="token operator">>>></span> <span class="token number">12</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> h <span class="token operator">^</span> <span class="token punctuation">(</span>h <span class="token operator">>>></span> <span class="token number">7</span><span class="token punctuation">)</span> <span class="token operator">^</span> <span class="token punctuation">(</span>h <span class="token operator">>>></span> <span class="token number">4</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span></code></pre><p>相比于 JDK1.8 的 hash 方法 ,JDK 1.7 的 hash 方法的性能会稍差一点点,因为毕竟扰动了 4 次。</p><p>所谓 <strong>“拉链法”</strong> 就是:将链表和数组相结合。也就是说创建一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。</p><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/jdk1.8%E4%B9%8B%E5%89%8D%E7%9A%84%E5%86%85%E9%83%A8%E7%BB%93%E6%9E%84-HashMap.jpg" alt="jdk1.8之前的内部结构-HashMap"></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://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/JDK1.8%E4%B9%8B%E5%90%8E%E7%9A%84HashMap%E5%BA%95%E5%B1%82%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84.jpg" alt="jdk1.8之后的内部结构-HashMap"></p><blockquote><p>TreeMap、TreeSet以及JDK1.8之后的HashMap底层都用到了红黑树。红黑树就是为了解决二叉查找树的缺陷,因为二叉查找树在某些情况下会退化成一个线性结构。</p></blockquote><p><strong>推荐阅读:</strong></p><ul><li>《Java 8系列之重新认识HashMap》 :<a href="https://zhuanlan.zhihu.com/p/21673805" target="_blank" rel="noopener">https://zhuanlan.zhihu.com/p/21673805</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==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" target="_blank" rel="noopener">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的结构一样,数组+链表/红黑二叉树。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>图片来源:<a href="http://www.cnblogs.com/chengxiao/p/6842045.html" target="_blank" rel="noopener">http://www.cnblogs.com/chengxiao/p/6842045.html</a></p><p><strong>HashTable:</strong></p><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/HashTable%E5%85%A8%E8%A1%A8%E9%94%81.png" alt="HashTable全表锁"></p><p><strong>JDK1.7的ConcurrentHashMap:</strong></p><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/ConcurrentHashMap%E5%88%86%E6%AE%B5%E9%94%81.jpg" alt="JDK1.7的ConcurrentHashMap"></p><p><strong>JDK1.8的ConcurrentHashMap(TreeBin: 红黑二叉树节点 Node: 链表节点):</strong></p><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/JDK1.8-ConcurrentHashMap-Structure.jpg" alt="JDK1.8的ConcurrentHashMap"></p><h2 id="ConcurrentHashMap线程安全的具体实现方式-底层具体实现"><a href="#ConcurrentHashMap线程安全的具体实现方式-底层具体实现" class="headerlink" title="ConcurrentHashMap线程安全的具体实现方式/底层具体实现"></a>ConcurrentHashMap线程安全的具体实现方式/底层具体实现</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><pre class=" language-java"><code class="language-java"><span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">Segment</span><span class="token operator">&lt;</span>K<span class="token punctuation">,</span>V<span class="token operator">></span> <span class="token keyword">extends</span> <span class="token class-name">ReentrantLock</span> <span class="token keyword">implements</span> <span class="token class-name">Serializable</span> <span class="token punctuation">{</span><span class="token punctuation">}</span></code></pre><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的结构类似,数组+链表/红黑二叉树。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><pre class=" language-java"><code class="language-java"> ArrayList<span class="token operator">&lt;</span>Integer<span class="token operator">></span> arrayList <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ArrayList</span><span class="token operator">&lt;</span>Integer<span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> arrayList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span> arrayList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">;</span> arrayList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">;</span> arrayList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token operator">-</span><span class="token number">5</span><span class="token punctuation">)</span><span class="token punctuation">;</span> arrayList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token number">7</span><span class="token punctuation">)</span><span class="token punctuation">;</span> arrayList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token number">4</span><span class="token punctuation">)</span><span class="token punctuation">;</span> arrayList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token operator">-</span><span class="token number">9</span><span class="token punctuation">)</span><span class="token punctuation">;</span> arrayList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token operator">-</span><span class="token number">7</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"原始数组:"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>arrayList<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// void reverse(List list):反转</span> Collections<span class="token punctuation">.</span><span class="token function">reverse</span><span class="token punctuation">(</span>arrayList<span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"Collections.reverse(arrayList):"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>arrayList<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// void sort(List list),按自然排序的升序排序</span> Collections<span class="token punctuation">.</span><span class="token function">sort</span><span class="token punctuation">(</span>arrayList<span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"Collections.sort(arrayList):"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>arrayList<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 定制排序的用法</span> Collections<span class="token punctuation">.</span><span class="token function">sort</span><span class="token punctuation">(</span>arrayList<span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name">Comparator</span><span class="token operator">&lt;</span>Integer<span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token annotation punctuation">@Override</span> <span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">compare</span><span class="token punctuation">(</span>Integer o1<span class="token punctuation">,</span> Integer o2<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> o2<span class="token punctuation">.</span><span class="token function">compareTo</span><span class="token punctuation">(</span>o1<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"定制排序后:"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>arrayList<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><p>Output:</p><pre><code>原始数组:[-1, 3, 3, -5, 7, 4, -9, -7]Collections.reverse(arrayList):[-7, -9, 4, 7, -5, 3, 3, -1]Collections.sort(arrayList):[-9, -7, -5, -1, 3, 3, 4, 7]定制排序后:[7, 4, 3, 3, -1, -5, -7, -9]</code></pre><h3 id="重写compareTo方法实现按年龄来排序"><a href="#重写compareTo方法实现按年龄来排序" class="headerlink" title="重写compareTo方法实现按年龄来排序"></a>重写compareTo方法实现按年龄来排序</h3><pre class=" language-java"><code class="language-java"><span class="token comment" spellcheck="true">// person对象没有实现Comparable接口,所以必须实现,这样才不会出错,才可以使treemap中的数据按顺序排列</span><span class="token comment" spellcheck="true">// 前面一个例子的String类已经默认实现了Comparable接口,详细可以查看String类的API文档,另外其他</span><span class="token comment" spellcheck="true">// 像Integer类等都已经实现了Comparable接口,所以不需要另外实现了</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Person</span> <span class="token keyword">implements</span> <span class="token class-name">Comparable</span><span class="token operator">&lt;</span>Person<span class="token operator">></span> <span class="token punctuation">{</span> <span class="token keyword">private</span> String name<span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">int</span> age<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token function">Person</span><span class="token punctuation">(</span>String name<span class="token punctuation">,</span> <span class="token keyword">int</span> age<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">super</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>name <span class="token operator">=</span> name<span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>age <span class="token operator">=</span> age<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> String <span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> name<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">setName</span><span class="token punctuation">(</span>String name<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>name <span class="token operator">=</span> name<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">getAge</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> age<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">setAge</span><span class="token punctuation">(</span><span class="token keyword">int</span> age<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>age <span class="token operator">=</span> age<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">/** * TODO重写compareTo方法实现按年龄来排序 */</span> <span class="token annotation punctuation">@Override</span> <span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">compareTo</span><span class="token punctuation">(</span>Person o<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// TODO Auto-generated method stub</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>age <span class="token operator">></span> o<span class="token punctuation">.</span><span class="token function">getAge</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token number">1</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>age <span class="token operator">&lt;</span> o<span class="token punctuation">.</span><span class="token function">getAge</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> age<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">}</span></code></pre><pre class=" language-java"><code class="language-java"> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span> TreeMap<span class="token operator">&lt;</span>Person<span class="token punctuation">,</span> String<span class="token operator">></span> pdata <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">TreeMap</span><span class="token operator">&lt;</span>Person<span class="token punctuation">,</span> String<span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> pdata<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Person</span><span class="token punctuation">(</span><span class="token string">"张三"</span><span class="token punctuation">,</span> <span class="token number">30</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token string">"zhangsan"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> pdata<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Person</span><span class="token punctuation">(</span><span class="token string">"李四"</span><span class="token punctuation">,</span> <span class="token number">20</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token string">"lisi"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> pdata<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Person</span><span class="token punctuation">(</span><span class="token string">"王五"</span><span class="token punctuation">,</span> <span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token string">"wangwu"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> pdata<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Person</span><span class="token punctuation">(</span><span class="token string">"小红"</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token string">"xiaohong"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 得到key的值的同时得到key所对应的值</span> Set<span class="token operator">&lt;</span>Person<span class="token operator">></span> keys <span class="token operator">=</span> pdata<span class="token punctuation">.</span><span class="token function">keySet</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">for</span> <span class="token punctuation">(</span>Person key <span class="token operator">:</span> keys<span class="token punctuation">)</span> <span class="token punctuation">{</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>key<span class="token punctuation">.</span><span class="token function">getAge</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">"-"</span> <span class="token operator">+</span> key<span class="token punctuation">.</span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre><p>Output:</p><pre><code>5-小红10-王五20-李四30-张三</code></pre><h2 id="集合框架底层数据结构总结"><a href="#集合框架底层数据结构总结" class="headerlink" title="集合框架底层数据结构总结"></a>集合框架底层数据结构总结</h2><h3 id="Collection"><a href="#Collection" class="headerlink" title="Collection"></a>Collection</h3><h4 id="1-List"><a href="#1-List" class="headerlink" title="1. List"></a>1. 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="2-Set"><a href="#2-Set" class="headerlink" title="2. Set"></a>2. 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" target="_blank" rel="noopener">《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="公众号"><a href="#公众号" class="headerlink" title="公众号"></a>公众号</h2><p>如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。</p><p><strong>《Java面试突击》:</strong> 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本<a href="#公众号">公众号</a>后台回复 <strong>“Java面试突击”</strong> 即可免费领取!</p><p><strong>Java工程师必备学习资源:</strong> 一些Java工程师常用学习资源公众号后台回复关键字 <strong>“1”</strong> 即可免费无套路获取。</p><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/167598cd2e17b8ec.png" alt="我的公众号"></p>]]></content>
<categories>
<category> Java集合框架 </category>
</categories>
<tags>
<tag> Java </tag>
<tag> Java集合框架 </tag>
</tags>
</entry>
<entry>
<title>面试必备!BIO,NIO,AIO 学习总结!</title>
<link href="/2019/08/21/java/java%E5%9F%BA%E7%A1%80/BIO,NIO,AIO%20%E6%80%BB%E7%BB%93/"/>
<url>/2019/08/21/java/java%E5%9F%BA%E7%A1%80/BIO,NIO,AIO%20%E6%80%BB%E7%BB%93/</url>
<content type="html"><![CDATA[<h1 id="BIO-NIO-AIO-总结"><a href="#BIO-NIO-AIO-总结" class="headerlink" title="BIO,NIO,AIO 总结"></a>BIO,NIO,AIO 总结</h1><p> Java 中的 BIO、NIO和 AIO 理解为是 Java 语言对操作系统的各种 IO 模型的封装。程序员在使用这些 API 的时候,不需要关心操作系统层面的知识,也不需要根据不同操作系统编写不同的代码。只需要使用Java的API就可以了。</p><p>在讲 BIO,NIO,AIO 之前先来回顾一下这样几个概念:同步与异步,阻塞与非阻塞。</p><p><strong>同步与异步</strong></p><ul><li><strong>同步:</strong> 同步就是发起一个调用后,被调用者未处理完请求之前,调用不返回。</li><li><strong>异步:</strong> 异步就是发起一个调用后,立刻得到被调用者的回应表示已接收到请求,但是被调用者并没有返回结果,此时我们可以处理其他的请求,被调用者通常依靠事件,回调等机制来通知调用者其返回结果。</li></ul><p>同步和异步的区别最大在于异步的话调用者不需要等待处理结果,被调用者会通过回调等机制来通知调用者其返回结果。</p><p><strong>阻塞和非阻塞</strong></p><ul><li><strong>阻塞:</strong> 阻塞就是发起一个请求,调用者一直等待请求结果返回,也就是当前线程会被挂起,无法从事其他任务,只有当条件就绪才能继续。</li><li><strong>非阻塞:</strong> 非阻塞就是发起一个请求,调用者不用一直等着结果返回,可以先去干其他事情。</li></ul><p>举个生活中简单的例子,你妈妈让你烧水,小时候你比较笨啊,在那里傻等着水开(<strong>同步阻塞</strong>)。等你稍微再长大一点,你知道每次烧水的空隙可以去干点其他事,然后只需要时不时来看看水开了没有(<strong>同步非阻塞</strong>)。后来,你们家用上了水开了会发出声音的壶,这样你就只需要听到响声后就知道水开了,在这期间你可以随便干自己的事情,你需要去倒水了(<strong>异步非阻塞</strong>)。</p><h2 id="1-BIO-Blocking-I-O"><a href="#1-BIO-Blocking-I-O" class="headerlink" title="1. BIO (Blocking I/O)"></a>1. BIO (Blocking I/O)</h2><p>同步阻塞I/O模式,数据的读取写入必须阻塞在一个线程内等待其完成。</p><h3 id="1-1-传统-BIO"><a href="#1-1-传统-BIO" class="headerlink" title="1.1 传统 BIO"></a>1.1 传统 BIO</h3><p>BIO通信(一请求一应答)模型图如下(图源网络,原出处不明):</p><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2.png" alt="传统BIO通信模型图"></p><p>采用 <strong>BIO 通信模型</strong> 的服务端,通常由一个独立的 Acceptor 线程负责监听客户端的连接。我们一般通过在<code>while(true)</code> 循环中服务端会调用 <code>accept()</code> 方法等待接收客户端的连接的方式监听请求,请求一旦接收到一个连接请求,就可以建立通信套接字在这个通信套接字上进行读写操作,此时不能再接收其他客户端连接请求,只能等待同当前连接的客户端的操作执行完成, 不过可以通过多线程来支持多个客户端的连接,如上图所示。</p><p>如果要让 <strong>BIO 通信模型</strong> 能够同时处理多个客户端请求,就必须使用多线程(主要原因是<code>socket.accept()</code>、<code>socket.read()</code>、<code>socket.write()</code> 涉及的三个主要函数都是同步阻塞的),也就是说它在接收到客户端连接请求之后为每个客户端创建一个新的线程进行链路处理,处理完成之后,通过输出流返回应答给客户端,线程销毁。这就是典型的 <strong>一请求一应答通信模型</strong> 。我们可以设想一下如果这个连接不做任何事情的话就会造成不必要的线程开销,不过可以通过 <strong>线程池机制</strong> 改善,线程池还可以让线程的创建和回收成本相对较低。使用<code>FixedThreadPool</code> 可以有效的控制了线程的最大数量,保证了系统有限的资源的控制,实现了N(客户端请求数量):M(处理客户端请求的线程数量)的伪异步I/O模型(N 可以远远大于 M),下面一节”伪异步 BIO”中会详细介绍到。</p><p><strong>我们再设想一下当客户端并发访问量增加后这种模型会出现什么问题?</strong></p><p>在 Java 虚拟机中,线程是宝贵的资源,线程的创建和销毁成本很高,除此之外,线程的切换成本也是很高的。尤其在 Linux 这样的操作系统中,线程本质上就是一个进程,创建和销毁线程都是重量级的系统函数。如果并发访问量增加会导致线程数急剧膨胀可能会导致线程堆栈溢出、创建新线程失败等问题,最终导致进程宕机或者僵死,不能对外提供服务。</p><h3 id="1-2-伪异步-IO"><a href="#1-2-伪异步-IO" class="headerlink" title="1.2 伪异步 IO"></a>1.2 伪异步 IO</h3><p>为了解决同步阻塞I/O面临的一个链路需要一个线程处理的问题,后来有人对它的线程模型进行了优化一一一后端通过一个线程池来处理多个客户端的请求接入,形成客户端个数M:线程池最大线程数N的比例关系,其中M可以远远大于N.通过线程池可以灵活地调配线程资源,设置线程的最大值,防止由于海量并发接入导致线程耗尽。</p><p>伪异步IO模型图(图源网络,原出处不明):</p><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/3.png" alt="伪异步IO模型图"></p><p>采用线程池和任务队列可以实现一种叫做伪异步的 I/O 通信框架,它的模型图如上图所示。当有新的客户端接入时,将客户端的 Socket 封装成一个Task(该任务实现java.lang.Runnable接口)投递到后端的线程池中进行处理,JDK 的线程池维护一个消息队列和 N 个活跃线程,对消息队列中的任务进行处理。由于线程池可以设置消息队列的大小和最大线程数,因此,它的资源占用是可控的,无论多少个客户端并发访问,都不会导致资源的耗尽和宕机。</p><p>伪异步I/O通信框架采用了线程池实现,因此避免了为每个请求都创建一个独立线程造成的线程资源耗尽问题。不过因为它的底层仍然是同步阻塞的BIO模型,因此无法从根本上解决问题。</p><h3 id="1-3-代码示例"><a href="#1-3-代码示例" class="headerlink" title="1.3 代码示例"></a>1.3 代码示例</h3><p>下面代码中演示了BIO通信(一请求一应答)模型。我们会在客户端创建多个线程依次连接服务端并向其发送”当前时间+:hello world”,服务端会为每个客户端线程创建一个线程来处理。代码示例出自闪电侠的博客,原地址如下: </p><p><a href="https://www.jianshu.com/p/a4e03835921a" target="_blank" rel="noopener">https://www.jianshu.com/p/a4e03835921a</a></p><p><strong>客户端</strong></p><pre class=" language-java"><code class="language-java"><span class="token comment" spellcheck="true">/** * * @author 闪电侠 * @date 2018年10月14日 * @Description:客户端 */</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">IOClient</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// TODO 创建多个线程,模拟多个客户端连接服务端</span> <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> Socket socket <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Socket</span><span class="token punctuation">(</span><span class="token string">"127.0.0.1"</span><span class="token punctuation">,</span> <span class="token number">3333</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> socket<span class="token punctuation">.</span><span class="token function">getOutputStream</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">write</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">": hello world"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getBytes</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> Thread<span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">2000</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Exception</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">IOException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">}</span></code></pre><p><strong>服务端</strong></p><pre class=" language-java"><code class="language-java"><span class="token comment" spellcheck="true">/** * @author 闪电侠 * @date 2018年10月14日 * @Description: 服务端 */</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">IOServer</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token keyword">throws</span> IOException <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// TODO 服务端处理客户端连接请求</span> ServerSocket serverSocket <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ServerSocket</span><span class="token punctuation">(</span><span class="token number">3333</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 接收到客户端连接请求之后为每个客户端创建一个新的线程进行链路处理</span> <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span> <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// 阻塞方法获取新的连接</span> Socket socket <span class="token operator">=</span> serverSocket<span class="token punctuation">.</span><span class="token function">accept</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 每一个新的连接都创建一个线程,负责读取数据</span> <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> <span class="token keyword">int</span> len<span class="token punctuation">;</span> <span class="token keyword">byte</span><span class="token punctuation">[</span><span class="token punctuation">]</span> data <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">byte</span><span class="token punctuation">[</span><span class="token number">1024</span><span class="token punctuation">]</span><span class="token punctuation">;</span> InputStream inputStream <span class="token operator">=</span> socket<span class="token punctuation">.</span><span class="token function">getInputStream</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 按字节流方式读取数据</span> <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>len <span class="token operator">=</span> inputStream<span class="token punctuation">.</span><span class="token function">read</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">!=</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">String</span><span class="token punctuation">(</span>data<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> len<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">IOException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">IOException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">}</span></code></pre><h3 id="1-4-总结"><a href="#1-4-总结" class="headerlink" title="1.4 总结"></a>1.4 总结</h3><p>在活动连接数不是特别高(小于单机1000)的情况下,这种模型是比较不错的,可以让每一个连接专注于自己的 I/O 并且编程模型简单,也不用过多考虑系统的过载、限流等问题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效的 I/O 处理模型来应对更高的并发量。</p><h2 id="2-NIO-New-I-O"><a href="#2-NIO-New-I-O" class="headerlink" title="2. NIO (New I/O)"></a>2. NIO (New I/O)</h2><h3 id="2-1-NIO-简介"><a href="#2-1-NIO-简介" class="headerlink" title="2.1 NIO 简介"></a>2.1 NIO 简介</h3><p> NIO是一种同步非阻塞的I/O模型,在Java 1.4 中引入了NIO框架,对应 java.nio 包,提供了 Channel , Selector,Buffer等抽象。</p><p>NIO中的N可以理解为Non-blocking,不单纯是New。它支持面向缓冲的,基于通道的I/O操作方法。 NIO提供了与传统BIO模型中的 <code>Socket</code> 和 <code>ServerSocket</code> 相对应的 <code>SocketChannel</code> 和 <code>ServerSocketChannel</code> 两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。对于低负载、低并发的应用程序,可以使用同步阻塞I/O来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发。</p><h3 id="2-2-NIO的特性-NIO与IO区别"><a href="#2-2-NIO的特性-NIO与IO区别" class="headerlink" title="2.2 NIO的特性/NIO与IO区别"></a>2.2 NIO的特性/NIO与IO区别</h3><p>如果是在面试中回答这个问题,我觉得首先肯定要从 NIO 流是非阻塞 IO 而 IO 流是阻塞 IO 说起。然后,可以从 NIO 的3个核心组件/特性为 NIO 带来的一些改进来分析。如果,你把这些都回答上了我觉得你对于 NIO 就有了更为深入一点的认识,面试官问到你这个问题,你也能很轻松的回答上来了。</p><h4 id="1-Non-blocking-IO(非阻塞IO)"><a href="#1-Non-blocking-IO(非阻塞IO)" class="headerlink" title="1)Non-blocking IO(非阻塞IO)"></a>1)Non-blocking IO(非阻塞IO)</h4><p><strong>IO流是阻塞的,NIO流是不阻塞的。</strong></p><p>Java NIO使我们可以进行非阻塞IO操作。比如说,单线程中从通道读取数据到buffer,同时可以继续做别的事情,当数据读取到buffer中后,线程再继续处理数据。写数据也是一样的。另外,非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。</p><p>Java IO的各种流是阻塞的。这意味着,当一个线程调用 <code>read()</code> 或 <code>write()</code> 时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了</p><h4 id="2-Buffer-缓冲区"><a href="#2-Buffer-缓冲区" class="headerlink" title="2)Buffer(缓冲区)"></a>2)Buffer(缓冲区)</h4><p><strong>IO 面向流(Stream oriented),而 NIO 面向缓冲区(Buffer oriented)。</strong></p><p>Buffer是一个对象,它包含一些要写入或者要读出的数据。在NIO类库中加入Buffer对象,体现了新库与原I/O的一个重要区别。在面向流的I/O中·可以将数据直接写入或者将数据直接读到 Stream 对象中。虽然 Stream 中也有 Buffer 开头的扩展类,但只是流的包装类,还是从流读到缓冲区,而 NIO 却是直接读到 Buffer 中进行操作。</p><p>在NIO厍中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的; 在写入数据时,写入到缓冲区中。任何时候访问NIO中的数据,都是通过缓冲区进行操作。</p><p>最常用的缓冲区是 ByteBuffer,一个 ByteBuffer 提供了一组功能用于操作 byte 数组。除了ByteBuffer,还有其他的一些缓冲区,事实上,每一种Java基本类型(除了Boolean类型)都对应有一种缓冲区。</p><h4 id="3-Channel-通道"><a href="#3-Channel-通道" class="headerlink" title="3)Channel (通道)"></a>3)Channel (通道)</h4><p>NIO 通过Channel(通道) 进行读写。</p><p>通道是双向的,可读也可写,而流的读写是单向的。无论读写,通道只能和Buffer交互。因为 Buffer,通道可以异步地读写。</p><h4 id="4-Selector-选择器"><a href="#4-Selector-选择器" class="headerlink" title="4)Selector (选择器)"></a>4)Selector (选择器)</h4><p>NIO有选择器,而IO没有。</p><p>选择器用于使用单个线程处理多个通道。因此,它需要较少的线程来处理这些通道。线程之间的切换对于操作系统来说是昂贵的。 因此,为了提高系统效率选择器是有用的。</p><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-2/Slector.png" alt="一个单线程中Selector维护3个Channel的示意图"></p><h3 id="2-3-NIO-读数据和写数据方式"><a href="#2-3-NIO-读数据和写数据方式" class="headerlink" title="2.3 NIO 读数据和写数据方式"></a>2.3 NIO 读数据和写数据方式</h3><p>通常来说NIO中的所有IO都是从 Channel(通道) 开始的。</p><ul><li>从通道进行数据读取 :创建一个缓冲区,然后请求通道读取数据。</li><li>从通道进行数据写入 :创建一个缓冲区,填充数据,并要求通道写入数据。</li></ul><p>数据读取和写入操作图示:</p><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-2/NIO%E8%AF%BB%E5%86%99%E6%95%B0%E6%8D%AE%E7%9A%84%E6%96%B9%E5%BC%8F.png" alt="NIO读写数据的方式"></p><h3 id="2-4-NIO核心组件简单介绍"><a href="#2-4-NIO核心组件简单介绍" class="headerlink" title="2.4 NIO核心组件简单介绍"></a>2.4 NIO核心组件简单介绍</h3><p>NIO 包含下面几个核心的组件:</p><ul><li>Channel(通道)</li><li>Buffer(缓冲区)</li><li>Selector(选择器)</li></ul><p>整个NIO体系包含的类远远不止这三个,只能说这三个是NIO体系的“核心API”。我们上面已经对这三个概念进行了基本的阐述,这里就不多做解释了。</p><h3 id="2-5-代码示例"><a href="#2-5-代码示例" class="headerlink" title="2.5 代码示例"></a>2.5 代码示例</h3><p>代码示例出自闪电侠的博客,原地址如下: </p><p><a href="https://www.jianshu.com/p/a4e03835921a" target="_blank" rel="noopener">https://www.jianshu.com/p/a4e03835921a</a></p><p>客户端 IOClient.java 的代码不变,我们对服务端使用 NIO 进行改造。以下代码较多而且逻辑比较复杂,大家看看就好。</p><pre class=" language-java"><code class="language-java"><span class="token comment" spellcheck="true">/** * * @author 闪电侠 * @date 2019年2月21日 * @Description: NIO 改造后的服务端 */</span><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">NIOServer</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token keyword">throws</span> IOException <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// 1. serverSelector负责轮询是否有新的连接,服务端监测到新的连接之后,不再创建一个新的线程,</span> <span class="token comment" spellcheck="true">// 而是直接将新连接绑定到clientSelector上,这样就不用 IO 模型中 1w 个 while 循环在死等</span> Selector serverSelector <span class="token operator">=</span> Selector<span class="token punctuation">.</span><span class="token function">open</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 2. clientSelector负责轮询连接是否有数据可读</span> Selector clientSelector <span class="token operator">=</span> Selector<span class="token punctuation">.</span><span class="token function">open</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// 对应IO编程中服务端启动</span> ServerSocketChannel listenerChannel <span class="token operator">=</span> ServerSocketChannel<span class="token punctuation">.</span><span class="token function">open</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> listenerChannel<span class="token punctuation">.</span><span class="token function">socket</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">bind</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">InetSocketAddress</span><span class="token punctuation">(</span><span class="token number">3333</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> listenerChannel<span class="token punctuation">.</span><span class="token function">configureBlocking</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span> listenerChannel<span class="token punctuation">.</span><span class="token function">register</span><span class="token punctuation">(</span>serverSelector<span class="token punctuation">,</span> SelectionKey<span class="token punctuation">.</span>OP_ACCEPT<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// 监测是否有新的连接,这里的1指的是阻塞的时间为 1ms</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>serverSelector<span class="token punctuation">.</span><span class="token function">select</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> Set<span class="token operator">&lt;</span>SelectionKey<span class="token operator">></span> set <span class="token operator">=</span> serverSelector<span class="token punctuation">.</span><span class="token function">selectedKeys</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> Iterator<span class="token operator">&lt;</span>SelectionKey<span class="token operator">></span> keyIterator <span class="token operator">=</span> set<span class="token punctuation">.</span><span class="token function">iterator</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">while</span> <span class="token punctuation">(</span>keyIterator<span class="token punctuation">.</span><span class="token function">hasNext</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> SelectionKey key <span class="token operator">=</span> keyIterator<span class="token punctuation">.</span><span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>key<span class="token punctuation">.</span><span class="token function">isAcceptable</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// (1) 每来一个新连接,不需要创建一个线程,而是直接注册到clientSelector</span> SocketChannel clientChannel <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>ServerSocketChannel<span class="token punctuation">)</span> key<span class="token punctuation">.</span><span class="token function">channel</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">accept</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> clientChannel<span class="token punctuation">.</span><span class="token function">configureBlocking</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span> clientChannel<span class="token punctuation">.</span><span class="token function">register</span><span class="token punctuation">(</span>clientSelector<span class="token punctuation">,</span> SelectionKey<span class="token punctuation">.</span>OP_READ<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span> keyIterator<span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">IOException</span> ignored<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span><span class="token operator">></span> <span class="token punctuation">{</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// (2) 批量轮询是否有哪些连接有数据可读,这里的1指的是阻塞的时间为 1ms</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>clientSelector<span class="token punctuation">.</span><span class="token function">select</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> Set<span class="token operator">&lt;</span>SelectionKey<span class="token operator">></span> set <span class="token operator">=</span> clientSelector<span class="token punctuation">.</span><span class="token function">selectedKeys</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> Iterator<span class="token operator">&lt;</span>SelectionKey<span class="token operator">></span> keyIterator <span class="token operator">=</span> set<span class="token punctuation">.</span><span class="token function">iterator</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">while</span> <span class="token punctuation">(</span>keyIterator<span class="token punctuation">.</span><span class="token function">hasNext</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> SelectionKey key <span class="token operator">=</span> keyIterator<span class="token punctuation">.</span><span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>key<span class="token punctuation">.</span><span class="token function">isReadable</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> SocketChannel clientChannel <span class="token operator">=</span> <span class="token punctuation">(</span>SocketChannel<span class="token punctuation">)</span> key<span class="token punctuation">.</span><span class="token function">channel</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> ByteBuffer byteBuffer <span class="token operator">=</span> ByteBuffer<span class="token punctuation">.</span><span class="token function">allocate</span><span class="token punctuation">(</span><span class="token number">1024</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// (3) 面向 Buffer</span> clientChannel<span class="token punctuation">.</span><span class="token function">read</span><span class="token punctuation">(</span>byteBuffer<span class="token punctuation">)</span><span class="token punctuation">;</span> byteBuffer<span class="token punctuation">.</span><span class="token function">flip</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span> Charset<span class="token punctuation">.</span><span class="token function">defaultCharset</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">newDecoder</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">decode</span><span class="token punctuation">(</span>byteBuffer<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span> keyIterator<span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> key<span class="token punctuation">.</span><span class="token function">interestOps</span><span class="token punctuation">(</span>SelectionKey<span class="token punctuation">.</span>OP_READ<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">IOException</span> ignored<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">}</span></code></pre><p>为什么大家都不愿意用 JDK 原生 NIO 进行开发呢?从上面的代码中大家都可以看出来,是真的难用!除了编程复杂、编程模型难之外,它还有以下让人诟病的问题:</p><ul><li>JDK 的 NIO 底层由 epoll 实现,该实现饱受诟病的空轮询 bug 会导致 cpu 飙升 100%</li><li>项目庞大之后,自行实现的 NIO 很容易出现各类 bug,维护成本较高,上面这一坨代码我都不能保证没有 bug</li></ul><p>Netty 的出现很大程度上改善了 JDK 原生 NIO 所存在的一些让人难以忍受的问题。</p><h3 id="3-AIO-Asynchronous-I-O"><a href="#3-AIO-Asynchronous-I-O" class="headerlink" title="3. AIO (Asynchronous I/O)"></a>3. AIO (Asynchronous I/O)</h3><p>AIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改进版 NIO 2,它是异步非阻塞的IO模型。异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。</p><p>AIO 是异步IO的缩写,虽然 NIO 在网络操作中,提供了非阻塞的方法,但是 NIO 的 IO 行为还是同步的。对于 NIO 来说,我们的业务线程是在 IO 操作准备好时,得到通知,接着就由这个线程自行进行 IO 操作,IO操作本身是同步的。(除了 AIO 其他的 IO 类型都是同步的,这一点可以从底层IO线程模型解释,推荐一篇文章:<a href="https://mp.weixin.qq.com/s?__biz=Mzg3MjA4MTExMw==&mid=2247484746&amp;idx=1&amp;sn=c0a7f9129d780786cabfcac0a8aa6bb7&source=41#wechat_redirect" target="_blank" rel="noopener">《漫话:如何给女朋友解释什么是Linux的五种IO模型?》</a> )</p><p>查阅网上相关资料,我发现就目前来说 AIO 的应用还不是很广泛,Netty 之前也尝试使用过 AIO,不过又放弃了。</p><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li>《Netty 权威指南》第二版</li><li><a href="https://zhuanlan.zhihu.com/p/23488863" target="_blank" rel="noopener">https://zhuanlan.zhihu.com/p/23488863</a> (美团技术团队)</li></ul>]]></content>
<categories>
<category> Java </category>
</categories>
<tags>
<tag> Java </tag>
<tag> NIO </tag>
<tag> IO </tag>
</tags>
</entry>
<entry>
<title>Java疑难点总结!!!</title>
<link href="/2019/08/20/java/java%E5%9F%BA%E7%A1%80/Java%E7%96%91%E9%9A%BE%E7%82%B9/"/>
<url>/2019/08/20/java/java%E5%9F%BA%E7%A1%80/Java%E7%96%91%E9%9A%BE%E7%82%B9/</url>
<content type="html"><![CDATA[<h1 id="1-基础"><a href="#1-基础" class="headerlink" title="1. 基础"></a>1. 基础</h1><h2 id="1-1-正确使用-equals-方法"><a href="#1-1-正确使用-equals-方法" class="headerlink" title="1.1. 正确使用 equals 方法"></a>1.1. 正确使用 equals 方法</h2><p>Object的equals方法容易抛空指针异常,应使用常量或确定有值的对象来调用 equals。 </p><p>举个例子:</p><pre class=" language-java"><code class="language-java"><span class="token comment" spellcheck="true">// 不能使用一个值为null的引用类型变量来调用非静态方法,否则会抛出异常</span>String str <span class="token operator">=</span> null<span class="token punctuation">;</span><span class="token keyword">if</span> <span class="token punctuation">(</span>str<span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span><span class="token string">"SnailClimb"</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">}</span></code></pre><p>运行上面的程序会抛出空指针异常,但是我们把第二行的条件判断语句改为下面这样的话,就不会抛出空指针异常,else 语句块得到执行。:</p><pre class=" language-java"><code class="language-java"><span class="token string">"SnailClimb"</span><span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span>str<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// false </span></code></pre><p>不过更推荐使用 <code>java.util.Objects#equals</code>(JDK7 引入的工具类)。</p><pre class=" language-java"><code class="language-java">Objects<span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span>null<span class="token punctuation">,</span><span class="token string">"SnailClimb"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// false</span></code></pre><p>我们看一下<code>java.util.Objects#equals</code>的源码就知道原因了。</p><pre class=" language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">boolean</span> <span class="token function">equals</span><span class="token punctuation">(</span>Object a<span class="token punctuation">,</span> Object b<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// 可以避免空指针异常。如果a==null的话此时a.equals(b)就不会得到执行,避免出现空指针异常。</span> <span class="token keyword">return</span> <span class="token punctuation">(</span>a <span class="token operator">==</span> b<span class="token punctuation">)</span> <span class="token operator">||</span> <span class="token punctuation">(</span>a <span class="token operator">!=</span> null <span class="token operator">&amp;&amp;</span> a<span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span>b<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre><p><strong>注意:</strong></p><p>Reference:<a href="https://blog.csdn.net/tick_tock97/article/details/72824894" target="_blank" rel="noopener">Java中equals方法造成空指针异常的原因及解决方案</a></p><ul><li>每种原始类型都有默认值一样,如int默认值为 0,boolean 的默认值为 false,null 是任何引用类型的默认值,不严格的说是所有 Object 类型的默认值。</li><li>可以使用 == 或者 != 操作来比较null值,但是不能使用其他算法或者逻辑操作。在Java中<code>null == null</code>将返回true。</li><li>不能使用一个值为null的引用类型变量来调用非静态方法,否则会抛出异常</li></ul><h2 id="1-2-整型包装类值的比较"><a href="#1-2-整型包装类值的比较" class="headerlink" title="1.2. 整型包装类值的比较"></a>1.2. 整型包装类值的比较</h2><p>所有整型包装类对象值的比较必须使用equals方法。</p><p>先看下面这个例子:</p><pre class=" language-java"><code class="language-java">Integer x <span class="token operator">=</span> <span class="token number">3</span><span class="token punctuation">;</span>Integer y <span class="token operator">=</span> <span class="token number">3</span><span class="token punctuation">;</span>System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>x <span class="token operator">==</span> y<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// true</span>Integer a <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Integer</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">;</span>Integer b <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Integer</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">;</span>System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>a <span class="token operator">==</span> b<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//false</span>System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>a<span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span>b<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//true</span></code></pre><p>当使用自动装箱方式创建一个Integer对象时,当数值在-128 ~127时,会将创建的 Integer 对象缓存起来,当下次再出现该数值时,直接从缓存中取出对应的Integer对象。所以上述代码中,x和y引用的是相同的Integer对象。 </p><p><strong>注意:</strong>如果你的IDE(IDEA/Eclipse)上安装了阿里巴巴的p3c插件,这个插件如果检测到你用 ==的话会报错提示,推荐安装一个这个插件,很不错。</p><h2 id="1-3-BigDecimal"><a href="#1-3-BigDecimal" class="headerlink" title="1.3. BigDecimal"></a>1.3. BigDecimal</h2><h3 id="1-3-1-BigDecimal-的用处"><a href="#1-3-1-BigDecimal-的用处" class="headerlink" title="1.3.1. BigDecimal 的用处"></a>1.3.1. BigDecimal 的用处</h3><p>《阿里巴巴Java开发手册》中提到:<strong>浮点数之间的等值判断,基本数据类型不能用==来比较,包装数据类型不能用 equals 来判断。</strong> 具体原理和浮点数的编码方式有关,这里就不多提了,我们下面直接上实例:</p><pre class=" language-java"><code class="language-java"><span class="token keyword">float</span> a <span class="token operator">=</span> <span class="token number">1.0f</span> <span class="token operator">-</span> <span class="token number">0.9f</span><span class="token punctuation">;</span><span class="token keyword">float</span> b <span class="token operator">=</span> <span class="token number">0.9f</span> <span class="token operator">-</span> <span class="token number">0.8f</span><span class="token punctuation">;</span>System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>a<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 0.100000024</span>System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>b<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 0.099999964</span>System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>a <span class="token operator">==</span> b<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// false</span></code></pre><p>具有基本数学知识的我们很清楚的知道输出并不是我们想要的结果(<strong>精度丢失</strong>),我们如何解决这个问题呢?一种很常用的方法是:<strong>使用使用 BigDecimal 来定义浮点数的值,再进行浮点数的运算操作。</strong></p><pre class=" language-java"><code class="language-java">BigDecimal a <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">BigDecimal</span><span class="token punctuation">(</span><span class="token string">"1.0"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>BigDecimal b <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">BigDecimal</span><span class="token punctuation">(</span><span class="token string">"0.9"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>BigDecimal c <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">BigDecimal</span><span class="token punctuation">(</span><span class="token string">"0.8"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>BigDecimal x <span class="token operator">=</span> a<span class="token punctuation">.</span><span class="token function">subtract</span><span class="token punctuation">(</span>b<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 0.1</span>BigDecimal y <span class="token operator">=</span> b<span class="token punctuation">.</span><span class="token function">subtract</span><span class="token punctuation">(</span>c<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 0.1</span>System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>x<span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span>y<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// true </span></code></pre><h3 id="1-3-2-BigDecimal-的大小比较"><a href="#1-3-2-BigDecimal-的大小比较" class="headerlink" title="1.3.2. BigDecimal 的大小比较"></a>1.3.2. BigDecimal 的大小比较</h3><p><code>a.compareTo(b)</code> : 返回 -1 表示小于,0 表示 等于, 1表示 大于。</p><pre class=" language-java"><code class="language-java">BigDecimal a <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">BigDecimal</span><span class="token punctuation">(</span><span class="token string">"1.0"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>BigDecimal b <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">BigDecimal</span><span class="token punctuation">(</span><span class="token string">"0.9"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>a<span class="token punctuation">.</span><span class="token function">compareTo</span><span class="token punctuation">(</span>b<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 1</span></code></pre><h3 id="1-3-3-BigDecimal-保留几位小数"><a href="#1-3-3-BigDecimal-保留几位小数" class="headerlink" title="1.3.3. BigDecimal 保留几位小数"></a>1.3.3. BigDecimal 保留几位小数</h3><p>通过 <code>setScale</code>方法设置保留几位小数以及保留规则。保留规则有挺多种,不需要记,IDEA会提示。</p><pre class=" language-java"><code class="language-java">BigDecimal m <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">BigDecimal</span><span class="token punctuation">(</span><span class="token string">"1.255433"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>BigDecimal n <span class="token operator">=</span> m<span class="token punctuation">.</span><span class="token function">setScale</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">,</span>BigDecimal<span class="token punctuation">.</span>ROUND_HALF_DOWN<span class="token punctuation">)</span><span class="token punctuation">;</span>System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>n<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">// 1.255</span></code></pre><h3 id="1-3-4-BigDecimal-的使用注意事项"><a href="#1-3-4-BigDecimal-的使用注意事项" class="headerlink" title="1.3.4. BigDecimal 的使用注意事项"></a>1.3.4. BigDecimal 的使用注意事项</h3><p>注意:我们在使用BigDecimal时,为了防止精度丢失,推荐使用它的 <strong>BigDecimal(String)</strong> 构造方法来创建对象。《阿里巴巴Java开发手册》对这部分内容也有提到如下图所示。</p><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019/7/BigDecimal.png" alt="《阿里巴巴Java开发手册》对这部分BigDecimal的描述"></p><h3 id="1-3-5-总结"><a href="#1-3-5-总结" class="headerlink" title="1.3.5. 总结"></a>1.3.5. 总结</h3><p>BigDecimal 主要用来操作(大)浮点数,BigInteger 主要用来操作大整数(超过 long 类型)。</p><p>BigDecimal 的实现利用到了 BigInteger, 所不同的是 BigDecimal 加入了小数位的概念</p><h2 id="1-4-基本数据类型与包装数据类型的使用标准"><a href="#1-4-基本数据类型与包装数据类型的使用标准" class="headerlink" title="1.4. 基本数据类型与包装数据类型的使用标准"></a>1.4. 基本数据类型与包装数据类型的使用标准</h2><p>Reference:《阿里巴巴Java开发手册》</p><ul><li>【强制】所有的 POJO 类属性必须使用包装数据类型。</li><li>【强制】RPC 方法的返回值和参数必须使用包装数据类型。</li><li>【推荐】所有的局部变量使用基本数据类型。</li></ul><p>比如我们如果自定义了一个Student类,其中有一个属性是成绩score,如果用Integer而不用int定义,一次考试,学生可能没考,值是null,也可能考了,但考了0分,值是0,这两个表达的状态明显不一样.</p><p><strong>说明</strong> :POJO 类属性没有初值是提醒使用者在需要使用时,必须自己显式地进行赋值,任何 NPE 问题,或者入库检查,都由使用者来保证。</p><p><strong>正例</strong> : 数据库的查询结果可能是 null,因为自动拆箱,用基本数据类型接收有 NPE 风险。</p><p><strong>反例</strong> : 比如显示成交总额涨跌情况,即正负 x%,x 为基本数据类型,调用的 RPC 服务,调用不成功时,返回的是默认值,页面显示为 0%,这是不合理的,应该显示成中划线。所以包装数据类型的 null 值,能够表示额外的信息,如:远程调用失败,异常退出。</p><h1 id="2-集合"><a href="#2-集合" class="headerlink" title="2. 集合"></a>2. 集合</h1><h2 id="2-1-Arrays-asList-使用指南"><a href="#2-1-Arrays-asList-使用指南" class="headerlink" title="2.1. Arrays.asList()使用指南"></a>2.1. Arrays.asList()使用指南</h2><p>最近使用<code>Arrays.asList()</code>遇到了一些坑,然后在网上看到这篇文章:<a href="http://javadevnotes.com/java-array-to-list-examples" target="_blank" rel="noopener">Java Array to List Examples</a> 感觉挺不错的,但是还不是特别全面。所以,自己对于这块小知识点进行了简单的总结。</p><h3 id="2-1-1-简介"><a href="#2-1-1-简介" class="headerlink" title="2.1.1. 简介"></a>2.1.1. 简介</h3><p><code>Arrays.asList()</code>在平时开发中还是比较常见的,我们可以使用它将一个数组转换为一个List集合。</p><pre class=" language-java"><code class="language-java">String<span class="token punctuation">[</span><span class="token punctuation">]</span> myArray <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token string">"Apple"</span><span class="token punctuation">,</span> <span class="token string">"Banana"</span><span class="token punctuation">,</span> <span class="token string">"Orange"</span> <span class="token punctuation">}</span>; List<span class="token operator">&lt;</span>String<span class="token operator">></span> myList <span class="token operator">=</span> Arrays<span class="token punctuation">.</span><span class="token function">asList</span><span class="token punctuation">(</span>myArray<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//上面两个语句等价于下面一条语句</span>List<span class="token operator">&lt;</span>String<span class="token operator">></span> myList <span class="token operator">=</span> Arrays<span class="token punctuation">.</span><span class="token function">asList</span><span class="token punctuation">(</span><span class="token string">"Apple"</span><span class="token punctuation">,</span><span class="token string">"Banana"</span><span class="token punctuation">,</span> <span class="token string">"Orange"</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><p>JDK 源码对于这个方法的说明:</p><pre class=" language-java"><code class="language-java"><span class="token comment" spellcheck="true">/** *返回由指定数组支持的固定大小的列表。此方法作为基于数组和基于集合的API之间的桥梁,与 Collection.toArray()结合使用。返回的List是可序列化并实现RandomAccess接口。 */</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token operator">&lt;</span>T<span class="token operator">></span> List<span class="token operator">&lt;</span>T<span class="token operator">></span> <span class="token function">asList</span><span class="token punctuation">(</span>T<span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span> a<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">ArrayList</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span>a<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span></code></pre><h3 id="2-1-2-《阿里巴巴Java-开发手册》对其的描述"><a href="#2-1-2-《阿里巴巴Java-开发手册》对其的描述" class="headerlink" title="2.1.2. 《阿里巴巴Java 开发手册》对其的描述"></a>2.1.2. 《阿里巴巴Java 开发手册》对其的描述</h3><p><code>Arrays.asList()</code>将数组转换为集合后,底层其实还是数组,《阿里巴巴Java 开发手册》对于这个方法有如下描述:</p><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/%E9%98%BF%E9%87%8C%E5%B7%B4%E5%B7%B4Java%E5%BC%80%E5%8F%91%E6%89%8B-Arrays.asList()%E6%96%B9%E6%B3%95.png" alt="阿里巴巴Java开发手-Arrays.asList()方法"></p><h3 id="2-1-3-使用时的注意事项总结"><a href="#2-1-3-使用时的注意事项总结" class="headerlink" title="2.1.3. 使用时的注意事项总结"></a>2.1.3. 使用时的注意事项总结</h3><p><strong>传递的数组必须是对象数组,而不是基本类型。</strong> </p><p><code>Arrays.asList()</code>是泛型方法,传入的对象必须是对象数组。</p><pre class=" language-java"><code class="language-java"><span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span> myArray <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">3</span> <span class="token punctuation">}</span><span class="token punctuation">;</span>List myList <span class="token operator">=</span> Arrays<span class="token punctuation">.</span><span class="token function">asList</span><span class="token punctuation">(</span>myArray<span class="token punctuation">)</span><span class="token punctuation">;</span>System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>myList<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//1</span>System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>myList<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//数组地址值</span>System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>myList<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//报错:ArrayIndexOutOfBoundsException</span><span class="token keyword">int</span> <span class="token punctuation">[</span><span class="token punctuation">]</span> array<span class="token operator">=</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span> myList<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>array<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//1</span></code></pre><p>当传入一个原生数据类型数组时,<code>Arrays.asList()</code> 的真正得到的参数就不是数组中的元素,而是数组对象本身!此时List 的唯一元素就是这个数组,这也就解释了上面的代码。</p><p>我们使用包装类型数组就可以解决这个问题。</p><pre class=" language-java"><code class="language-java">Integer<span class="token punctuation">[</span><span class="token punctuation">]</span> myArray <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">3</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre><p><strong>使用集合的修改方法:<code>add()</code>、<code>remove()</code>、<code>clear()</code>会抛出异常。</strong></p><pre class=" language-java"><code class="language-java">List myList <span class="token operator">=</span> Arrays<span class="token punctuation">.</span><span class="token function">asList</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">;</span>myList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token number">4</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//运行时报错:UnsupportedOperationException</span>myList<span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//运行时报错:UnsupportedOperationException</span>myList<span class="token punctuation">.</span><span class="token function">clear</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//运行时报错:UnsupportedOperationException</span></code></pre><p><code>Arrays.asList()</code> 方法返回的并不是 <code>java.util.ArrayList</code> ,而是 <code>java.util.Arrays</code> 的一个内部类,这个内部类并没有实现集合的修改方法或者说并没有重写这些方法。</p><pre class=" language-java"><code class="language-java">List myList <span class="token operator">=</span> Arrays<span class="token punctuation">.</span><span class="token function">asList</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">;</span>System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>myList<span class="token punctuation">.</span><span class="token function">getClass</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//class java.util.Arrays$ArrayList</span></code></pre><p>下图是<code>java.util.Arrays$ArrayList</code>的简易源码,我们可以看到这个类重写的方法有哪些。</p><pre class=" language-java"><code class="language-java"> <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">ArrayList</span><span class="token operator">&lt;</span>E<span class="token operator">></span> <span class="token keyword">extends</span> <span class="token class-name">AbstractList</span><span class="token operator">&lt;</span>E<span class="token operator">></span> <span class="token keyword">implements</span> <span class="token class-name">RandomAccess</span><span class="token punctuation">,</span> java<span class="token punctuation">.</span>io<span class="token punctuation">.</span>Serializable <span class="token punctuation">{</span> <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span> <span class="token annotation punctuation">@Override</span> <span class="token keyword">public</span> E <span class="token function">get</span><span class="token punctuation">(</span><span class="token keyword">int</span> index<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span> <span class="token punctuation">}</span> <span class="token annotation punctuation">@Override</span> <span class="token keyword">public</span> E <span class="token function">set</span><span class="token punctuation">(</span><span class="token keyword">int</span> index<span class="token punctuation">,</span> E element<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span> <span class="token punctuation">}</span> <span class="token annotation punctuation">@Override</span> <span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token function">indexOf</span><span class="token punctuation">(</span>Object o<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span> <span class="token punctuation">}</span> <span class="token annotation punctuation">@Override</span> <span class="token keyword">public</span> <span class="token keyword">boolean</span> <span class="token function">contains</span><span class="token punctuation">(</span>Object o<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span> <span class="token punctuation">}</span> <span class="token annotation punctuation">@Override</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">forEach</span><span class="token punctuation">(</span>Consumer<span class="token operator">&lt;</span><span class="token operator">?</span> <span class="token keyword">super</span> E<span class="token operator">></span> action<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span> <span class="token punctuation">}</span> <span class="token annotation punctuation">@Override</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">replaceAll</span><span class="token punctuation">(</span>UnaryOperator<span class="token operator">&lt;</span>E<span class="token operator">></span> operator<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span> <span class="token punctuation">}</span> <span class="token annotation punctuation">@Override</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">sort</span><span class="token punctuation">(</span>Comparator<span class="token operator">&lt;</span><span class="token operator">?</span> <span class="token keyword">super</span> E<span class="token operator">></span> c<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre><p>我们再看一下<code>java.util.AbstractList</code>的<code>remove()</code>方法,这样我们就明白为啥会抛出<code>UnsupportedOperationException</code>。</p><pre class=" language-java"><code class="language-java"><span class="token keyword">public</span> E <span class="token function">remove</span><span class="token punctuation">(</span><span class="token keyword">int</span> index<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">UnsupportedOperationException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span></code></pre><h3 id="2-1-4-如何正确的将数组转换为ArrayList"><a href="#2-1-4-如何正确的将数组转换为ArrayList" class="headerlink" title="2.1.4. 如何正确的将数组转换为ArrayList?"></a>2.1.4. 如何正确的将数组转换为ArrayList?</h3><p>stackoverflow:<a href="https://dwz.cn/vcBkTiTW" target="_blank" rel="noopener">https://dwz.cn/vcBkTiTW</a></p><p><strong>1. 自己动手实现(教育目的)</strong></p><pre class=" language-java"><code class="language-java"><span class="token comment" spellcheck="true">//JDK1.5+</span><span class="token keyword">static</span> <span class="token operator">&lt;</span>T<span class="token operator">></span> List<span class="token operator">&lt;</span>T<span class="token operator">></span> <span class="token function">arrayToList</span><span class="token punctuation">(</span><span class="token keyword">final</span> T<span class="token punctuation">[</span><span class="token punctuation">]</span> array<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">final</span> List<span class="token operator">&lt;</span>T<span class="token operator">></span> l <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ArrayList</span><span class="token operator">&lt;</span>T<span class="token operator">></span><span class="token punctuation">(</span>array<span class="token punctuation">.</span>length<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">final</span> T s <span class="token operator">:</span> array<span class="token punctuation">)</span> <span class="token punctuation">{</span> l<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>s<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> <span class="token punctuation">(</span>l<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span></code></pre><pre class=" language-java"><code class="language-java">Integer <span class="token punctuation">[</span><span class="token punctuation">]</span> myArray <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">3</span> <span class="token punctuation">}</span><span class="token punctuation">;</span>System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token function">arrayToList</span><span class="token punctuation">(</span>myArray<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getClass</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//class java.util.ArrayList</span></code></pre><p><strong>2. 最简便的方法(推荐)</strong></p><pre class=" language-java"><code class="language-java">List list <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ArrayList</span><span class="token operator">&lt;</span><span class="token operator">></span><span class="token punctuation">(</span>Arrays<span class="token punctuation">.</span><span class="token function">asList</span><span class="token punctuation">(</span><span class="token string">"a"</span><span class="token punctuation">,</span> <span class="token string">"b"</span><span class="token punctuation">,</span> <span class="token string">"c"</span><span class="token punctuation">)</span><span class="token punctuation">)</span></code></pre><p><strong>3. 使用 Java8 的Stream(推荐)</strong></p><pre class=" language-java"><code class="language-java">Integer <span class="token punctuation">[</span><span class="token punctuation">]</span> myArray <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">3</span> <span class="token punctuation">}</span><span class="token punctuation">;</span>List myList <span class="token operator">=</span> Arrays<span class="token punctuation">.</span><span class="token function">stream</span><span class="token punctuation">(</span>myArray<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">collect</span><span class="token punctuation">(</span>Collectors<span class="token punctuation">.</span><span class="token function">toList</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//基本类型也可以实现转换(依赖boxed的装箱操作)</span><span class="token keyword">int</span> <span class="token punctuation">[</span><span class="token punctuation">]</span> myArray2 <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">3</span> <span class="token punctuation">}</span><span class="token punctuation">;</span>List myList <span class="token operator">=</span> Arrays<span class="token punctuation">.</span><span class="token function">stream</span><span class="token punctuation">(</span>myArray2<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">boxed</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">collect</span><span class="token punctuation">(</span>Collectors<span class="token punctuation">.</span><span class="token function">toList</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><p><strong>4. 使用 Guava(推荐)</strong></p><p>对于不可变集合,你可以使用<a href="https://github.com/google/guava/blob/master/guava/src/com/google/common/collect/ImmutableList.java" target="_blank" rel="noopener"><code>ImmutableList</code></a>类及其<a href="https://github.com/google/guava/blob/master/guava/src/com/google/common/collect/ImmutableList.java#L101" target="_blank" rel="noopener"><code>of()</code></a>与<a href="https://github.com/google/guava/blob/master/guava/src/com/google/common/collect/ImmutableList.java#L225" target="_blank" rel="noopener"><code>copyOf()</code></a>工厂方法:(参数不能为空)</p><pre class=" language-java"><code class="language-java">List<span class="token operator">&lt;</span>String<span class="token operator">></span> il <span class="token operator">=</span> ImmutableList<span class="token punctuation">.</span><span class="token function">of</span><span class="token punctuation">(</span><span class="token string">"string"</span><span class="token punctuation">,</span> <span class="token string">"elements"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// from varargs</span>List<span class="token operator">&lt;</span>String<span class="token operator">></span> il <span class="token operator">=</span> ImmutableList<span class="token punctuation">.</span><span class="token function">copyOf</span><span class="token punctuation">(</span>aStringArray<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// from array</span></code></pre><p>对于可变集合,你可以使用<a href="https://github.com/google/guava/blob/master/guava/src/com/google/common/collect/Lists.java" target="_blank" rel="noopener"><code>Lists</code></a>类及其<a href="https://github.com/google/guava/blob/master/guava/src/com/google/common/collect/Lists.java#L87" target="_blank" rel="noopener"><code>newArrayList()</code></a>工厂方法:</p><pre class=" language-java"><code class="language-java">List<span class="token operator">&lt;</span>String<span class="token operator">></span> l1 <span class="token operator">=</span> Lists<span class="token punctuation">.</span><span class="token function">newArrayList</span><span class="token punctuation">(</span>anotherListOrCollection<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// from collection</span>List<span class="token operator">&lt;</span>String<span class="token operator">></span> l2 <span class="token operator">=</span> Lists<span class="token punctuation">.</span><span class="token function">newArrayList</span><span class="token punctuation">(</span>aStringArray<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// from array</span>List<span class="token operator">&lt;</span>String<span class="token operator">></span> l3 <span class="token operator">=</span> Lists<span class="token punctuation">.</span><span class="token function">newArrayList</span><span class="token punctuation">(</span><span class="token string">"or"</span><span class="token punctuation">,</span> <span class="token string">"string"</span><span class="token punctuation">,</span> <span class="token string">"elements"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// from varargs</span></code></pre><p><strong>5. 使用 Apache Commons Collections</strong></p><pre class=" language-java"><code class="language-java">List<span class="token operator">&lt;</span>String<span class="token operator">></span> list <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ArrayList</span><span class="token operator">&lt;</span>String<span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>CollectionUtils<span class="token punctuation">.</span><span class="token function">addAll</span><span class="token punctuation">(</span>list<span class="token punctuation">,</span> str<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><h2 id="2-2-Collection-toArray-方法使用的坑-amp-如何反转数组"><a href="#2-2-Collection-toArray-方法使用的坑-amp-如何反转数组" class="headerlink" title="2.2. Collection.toArray()方法使用的坑&amp;如何反转数组"></a>2.2. Collection.toArray()方法使用的坑&amp;如何反转数组</h2><p>该方法是一个泛型方法:<code>&lt;T&gt; T[] toArray(T[] a);</code> 如果<code>toArray</code>方法中没有传递任何参数的话返回的是<code>Object</code>类型数组。</p><pre class=" language-java"><code class="language-java">String <span class="token punctuation">[</span><span class="token punctuation">]</span> s<span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">String</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">{</span> <span class="token string">"dog"</span><span class="token punctuation">,</span> <span class="token string">"lazy"</span><span class="token punctuation">,</span> <span class="token string">"a"</span><span class="token punctuation">,</span> <span class="token string">"over"</span><span class="token punctuation">,</span> <span class="token string">"jumps"</span><span class="token punctuation">,</span> <span class="token string">"fox"</span><span class="token punctuation">,</span> <span class="token string">"brown"</span><span class="token punctuation">,</span> <span class="token string">"quick"</span><span class="token punctuation">,</span> <span class="token string">"A"</span><span class="token punctuation">}</span><span class="token punctuation">;</span>List<span class="token operator">&lt;</span>String<span class="token operator">></span> list <span class="token operator">=</span> Arrays<span class="token punctuation">.</span><span class="token function">asList</span><span class="token punctuation">(</span>s<span class="token punctuation">)</span><span class="token punctuation">;</span>Collections<span class="token punctuation">.</span><span class="token function">reverse</span><span class="token punctuation">(</span>list<span class="token punctuation">)</span><span class="token punctuation">;</span>s<span class="token operator">=</span>list<span class="token punctuation">.</span><span class="token function">toArray</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">String</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment" spellcheck="true">//没有指定类型的话会报错</span></code></pre><p>由于JVM优化,<code>new String[0]</code>作为<code>Collection.toArray()</code>方法的参数现在使用更好,<code>new String[0]</code>就是起一个模板的作用,指定了返回数组的类型,0是为了节省空间,因为它只是为了说明返回的类型。详见:<a href="https://shipilev.net/blog/2016/arrays-wisdom-ancients/" target="_blank" rel="noopener">https://shipilev.net/blog/2016/arrays-wisdom-ancients/</a></p><h2 id="2-3-不要在-foreach-循环里进行元素的-remove-add-操作"><a href="#2-3-不要在-foreach-循环里进行元素的-remove-add-操作" class="headerlink" title="2.3. 不要在 foreach 循环里进行元素的 remove/add 操作"></a>2.3. 不要在 foreach 循环里进行元素的 remove/add 操作</h2><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://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019/7/foreach-remove:add.png" alt="不要在 foreach 循环里进行元素的 remove/add 操作">****</p>]]></content>
<categories>
<category> Java </category>
</categories>
<tags>
<tag> Java </tag>
<tag> NIO </tag>
<tag> IO </tag>
</tags>
</entry>
<entry>
<title>做了一个很久没敢做的事情</title>
<link href="/2019/01/02/chat/%E5%81%9A%E4%BA%86%E4%B8%80%E4%B8%AA%E5%BE%88%E4%B9%85%E6%B2%A1%E6%95%A2%E5%81%9A%E7%9A%84%E4%BA%8B%E6%83%85/"/>
<url>/2019/01/02/chat/%E5%81%9A%E4%BA%86%E4%B8%80%E4%B8%AA%E5%BE%88%E4%B9%85%E6%B2%A1%E6%95%A2%E5%81%9A%E7%9A%84%E4%BA%8B%E6%83%85/</url>
<content type="html"><![CDATA[<p>不知道看到这篇文章的老哥们有多少加过 Guide 哥创建的群呢?</p><p>这里简单说一下我为啥叫 Guide 哥吧!</p><blockquote><p> 哈哈,主要是为了读者更方便称呼故自称 Guide 哥。ps:之前很多人喊我 Gudie 哥,感觉听着还挺顺口,就直接用了这个昵称。</p><p>另外,基本从我开始在网上写文章,我就一直用 SnailClimb这个名字,很多人不知道这个名字是啥意思,给大家拆解一下就清楚了。SnailClimb=Snail(蜗牛)+Climb(攀登)。我从小就非常喜欢听周杰伦的歌曲,也非常喜欢他的《蜗牛》🐌这首歌曲,所以,当年我高考发挥的算是比较失常,上了大学之后还是比较奋青,所以给自己起名字叫 SnailClimb ,寓意自己要不断向上攀登,哈哈。</p></blockquote><p><strong>我做了什么事情呢?</strong></p><p>我创建了一个知识星球。有别于微信群的实时交流,目前来看这是只是沉淀最好的一种方式了。</p><p>我创建知识星球主要是为了 <strong>加深和大家的交流以及将自己觉得不错的知识沉淀下来</strong> 。微信群在这方面太不好友好了,微信群只适合用来实时交流。我无法保证知识星球的球友的问题我都能回答或者说我都有能力/时间回答,我可以保证的是我输出在知识星球的一些内容对大家肯定是帮助的。</p><p>知识星球的最低定价是 50,我觉得太贵了,并不是说这个星球的价值不止这个数,我觉得价值远超,真的远超。所以,我就申请了 33 元的优惠卷,优惠卷地址:<a href="https://t.zsxq.com/iIqZBUR" target="_blank" rel="noopener">https://t.zsxq.com/iIqZBUR</a> 。</p><p>另外,没有创建免费的原因是为了提高进入的标准,17 元的定价(平台还会抽取一部分)于我来说并不是为了赚钱。我发的福利可能都远远超过这个数。</p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-11/javaguide-读者圈.png" style="zoom:50%;" /><p>开春来了之后,可以预知的是有大量程序员或者准程序要面临找工作/找实习,很多是被辞退的(在我看来因为个人能力辞退的占少部分,大部分还是由于公司的一些变动),很多是向去追求自己更满意的工作的俗称跳槽。</p><p>我们这个行业的跳槽率以及跳槽成功的几率都还挺高,毕竟跳槽的性价比总体来说还是很不错的。</p><p>所以我目前我在知识星球正在做的是整理一个对面试和学习 Java 都有帮助的系列文章集合,里面的文章大部分还是会是我自己的,大部分可能还是我之前写过的,不过整理出来会更加系统、更加全面。</p><p>Ps:是不是要做知识星球这件事情我犹豫了有半年的时间,害怕自己没时间做,害怕自己没能力做好,反正就是各种担心,最终在某个晚上,纠结了半天之后,我还是决定尝试一下!</p><p>加油!奥利给!不要犹豫,干就完事了。</p>]]></content>
<categories>
<category> 杂记 </category>
</categories>
<tags>
<tag> 杂记 </tag>
</tags>
</entry>
<entry>
<title>可能是你见过总结的最好的 Java 基础知识点汇总!!!</title>
<link href="/2018/09/18/java/java%E5%9F%BA%E7%A1%80/Java%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86/"/>
<url>/2018/09/18/java/java%E5%9F%BA%E7%A1%80/Java%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86/</url>
<content type="html"><![CDATA[<h2 id="1-面向对象和面向过程的区别"><a href="#1-面向对象和面向过程的区别" class="headerlink" title="1. 面向对象和面向过程的区别"></a>1. 面向对象和面向过程的区别</h2><ul><li><strong>面向过程</strong> :<strong>面向过程性能比面向对象高。</strong> 因为类调用时需要实例化,开销比较大,比较消耗资源,所以当性能是最重要的考量因素的时候,比如单片机、嵌入式开发、Linux/Unix等一般采用面向过程开发。但是,<strong>面向过程没有面向对象易维护、易复用、易扩展。</strong> </li><li><strong>面向对象</strong> :<strong>面向对象易维护、易复用、易扩展。</strong> 因为面向对象有封装、继承、多态性的特性,所以可以设计出低耦合的系统,使系统更加灵活、更加易于维护。但是,<strong>面向对象性能比面向过程低</strong>。</li></ul><p>参见 issue : <a href="https://github.com/Snailclimb/JavaGuide/issues/431" target="_blank" rel="noopener">面向过程 :面向过程性能比面向对象高??</a></p><blockquote><p>这个并不是根本原因,面向过程也需要分配内存,计算内存偏移量,Java性能差的主要原因并不是因为它是面向对象语言,而是Java是半编译语言,最终的执行代码并不是可以直接被CPU执行的二进制机械码。</p><p>而面向过程语言大多都是直接编译成机械码在电脑上执行,并且其它一些面向过程的脚本语言性能也并不一定比Java好。</p></blockquote><h2 id="2-Java-语言有哪些特点"><a href="#2-Java-语言有哪些特点" class="headerlink" title="2. Java 语言有哪些特点?"></a>2. Java 语言有哪些特点?</h2><ol><li>简单易学;</li><li>面向对象(封装,继承,多态);</li><li>平台无关性( Java 虚拟机实现平台无关性);</li><li>可靠性;</li><li>安全性;</li><li>支持多线程( C++ 语言没有内置的多线程机制,因此必须调用操作系统的多线程功能来进行多线程程序设计,而 Java 语言却提供了多线程支持);</li><li>支持网络编程并且很方便( Java 语言诞生本身就是为简化网络编程设计的,因此 Java 语言不仅支持网络编程而且很方便);</li><li>编译与解释并存;</li></ol><blockquote><p>修正(参见: <a href="https://github.com/Snailclimb/JavaGuide/issues/544" target="_blank" rel="noopener">issue#544</a>):C++11开始(2011年的时候),C++就引入了多线程库,在windows、linux、macos都可以使用<code>std::thread</code>和<code>std::async</code>来创建线程。参考链接:<a href="http://www.cplusplus.com/reference/thread/thread/?kw=thread" target="_blank" rel="noopener">http://www.cplusplus.com/reference/thread/thread/?kw=thread</a></p></blockquote><h2 id="3-关于-JVM-JDK-和-JRE-最详细通俗的解答"><a href="#3-关于-JVM-JDK-和-JRE-最详细通俗的解答" class="headerlink" title="3. 关于 JVM JDK 和 JRE 最详细通俗的解答"></a>3. 关于 JVM JDK 和 JRE 最详细通俗的解答</h2><h3 id="JVM"><a href="#JVM" class="headerlink" title="JVM"></a>JVM</h3><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></p><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/Java%20%E7%A8%8B%E5%BA%8F%E8%BF%90%E8%A1%8C%E8%BF%87%E7%A8%8B.png" alt="Java程序运行过程"></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>总结:</strong></p><p>Java虚拟机(JVM)是运行 Java 字节码的虚拟机。JVM有针对不同系统的特定实现(Windows,Linux,macOS),目的是使用相同的字节码,它们都会给出相同的结果。字节码和不同系统的 JVM 实现是 Java 语言“一次编译,随处可以运行”的关键所在。 </p><h3 id="JDK-和-JRE"><a href="#JDK-和-JRE" class="headerlink" title="JDK 和 JRE"></a>JDK 和 JRE</h3><p>JDK是Java Development Kit,它是功能齐全的Java SDK。它拥有JRE所拥有的一切,还有编译器(javac)和工具(如javadoc和jdb)。它能够创建和编译程序。</p><p>JRE 是 Java运行时环境。它是运行已编译 Java 程序所需的所有内容的集合,包括 Java虚拟机(JVM),Java类库,java命令和其他的一些基础构件。但是,它不能用于创建新程序。</p><p>如果你只是为了运行一下 Java 程序的话,那么你只需要安装 JRE 就可以了。如果你需要进行一些 Java 编程方面的工作,那么你就需要安装JDK了。但是,这不是绝对的。有时,即使您不打算在计算机上进行任何Java开发,仍然需要安装JDK。例如,如果要使用JSP部署Web应用程序,那么从技术上讲,您只是在应用程序服务器中运行Java程序。那你为什么需要JDK呢?因为应用程序服务器会将 JSP 转换为 Java servlet,并且需要使用 JDK 来编译 servlet。</p><h2 id="4-Oracle-JDK-和-OpenJDK-的对比"><a href="#4-Oracle-JDK-和-OpenJDK-的对比" class="headerlink" title="4. Oracle JDK 和 OpenJDK 的对比"></a>4. Oracle JDK 和 OpenJDK 的对比</h2><p>可能在看这个问题之前很多人和我一样并没有接触和使用过 OpenJDK 。那么Oracle和OpenJDK之间是否存在重大差异?下面我通过收集到的一些资料,为你解答这个被很多人忽视的问题。</p><p>对于Java 7,没什么关键的地方。OpenJDK项目主要基于Sun捐赠的HotSpot源代码。此外,OpenJDK被选为Java 7的参考实现,由Oracle工程师维护。关于JVM,JDK,JRE和OpenJDK之间的区别,Oracle博客帖子在2012年有一个更详细的答案:</p><blockquote><p>问:OpenJDK存储库中的源代码与用于构建Oracle JDK的代码之间有什么区别?</p><p>答:非常接近 - 我们的Oracle JDK版本构建过程基于OpenJDK 7构建,只添加了几个部分,例如部署代码,其中包括Oracle的Java插件和Java WebStart的实现,以及一些封闭的源代码派对组件,如图形光栅化器,一些开源的第三方组件,如Rhino,以及一些零碎的东西,如附加文档或第三方字体。展望未来,我们的目的是开源Oracle JDK的所有部分,除了我们考虑商业功能的部分。</p></blockquote><p><strong>总结:</strong></p><ol><li>Oracle JDK大概每6个月发一次主要版本,而OpenJDK版本大概每三个月发布一次。但这不是固定的,我觉得了解这个没啥用处。详情参见:<a href="https://blogs.oracle.com/java-platform-group/update-and-faq-on-the-java-se-release-cadence。" target="_blank" rel="noopener">https://blogs.oracle.com/java-platform-group/update-and-faq-on-the-java-se-release-cadence。</a></li><li>OpenJDK 是一个参考模型并且是完全开源的,而Oracle JDK是OpenJDK的一个实现,并不是完全开源的;</li><li>Oracle JDK 比 OpenJDK 更稳定。OpenJDK和Oracle JDK的代码几乎相同,但Oracle JDK有更多的类和一些错误修复。因此,如果您想开发企业/商业软件,我建议您选择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="5-Java和C-的区别"><a href="#5-Java和C-的区别" class="headerlink" title="5. Java和C++的区别?"></a>5. Java和C++的区别?</h2><p>我知道很多人没学过 C++,但是面试官就是没事喜欢拿咱们 Java 和 C++ 比呀!没办法!!!就算没学过C++,也要记下来!</p><ul><li>都是面向对象的语言,都支持封装、继承和多态</li><li>Java 不提供指针来直接访问内存,程序内存更加安全</li><li>Java 的类是单继承的,C++ 支持多重继承;虽然 Java 的类不可以多继承,但是接口可以多继承。</li><li>Java 有自动内存管理机制,不需要程序员手动释放无用内存</li></ul><h2 id="6-什么是-Java-程序的主类-应用程序和小程序的主类有何不同"><a href="#6-什么是-Java-程序的主类-应用程序和小程序的主类有何不同" class="headerlink" title="6. 什么是 Java 程序的主类 应用程序和小程序的主类有何不同?"></a>6. 什么是 Java 程序的主类 应用程序和小程序的主类有何不同?</h2><p>一个程序中可以有多个类,但只能有一个类是主类。在 Java 应用程序中,这个主类是指包含 main()方法的类。而在 Java 小程序中,这个主类是一个继承自系统类 JApplet 或 Applet 的子类。应用程序的主类不一定要求是 public 类,但小程序的主类要求必须是 public 类。主类是 Java 程序执行的入口点。</p><h2 id="7-Java-应用程序与小程序之间有哪些差别"><a href="#7-Java-应用程序与小程序之间有哪些差别" class="headerlink" title="7. Java 应用程序与小程序之间有哪些差别?"></a>7. Java 应用程序与小程序之间有哪些差别?</h2><p>简单说应用程序是从主线程启动(也就是 <code>main()</code> 方法)。applet 小程序没有 <code>main()</code> 方法,主要是嵌在浏览器页面上运行(调用<code>init()</code>或者<code>run()</code>来启动),嵌入浏览器这点跟 flash 的小游戏类似。</p><h2 id="8-字符型常量和字符串常量的区别"><a href="#8-字符型常量和字符串常量的区别" class="headerlink" title="8. 字符型常量和字符串常量的区别?"></a>8. 字符型常量和字符串常量的区别?</h2><ol><li>形式上: 字符常量是单引号引起的一个字符; 字符串常量是双引号引起的若干个字符</li><li>含义上: 字符常量相当于一个整型值( ASCII 值),可以参加表达式运算; 字符串常量代表一个地址值(该字符串在内存中存放位置)</li><li>占内存大小 字符常量只占2个字节; 字符串常量占若干个字节(至少一个字符结束标志) (<strong>注意: char在Java中占两个字节</strong>)</li></ol><blockquote><p>java编程思想第四版:2.2.2节<br><img src="http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-15/86735519.jpg" alt=""></p></blockquote><h2 id="9-构造器-Constructor-是否可被-override"><a href="#9-构造器-Constructor-是否可被-override" class="headerlink" title="9. 构造器 Constructor 是否可被 override?"></a>9. 构造器 Constructor 是否可被 override?</h2><p>在讲继承的时候我们就知道父类的私有属性和构造方法并不能被继承,所以 Constructor 也就不能被 override(重写),但是可以 overload(重载),所以你可以看到一个类中有多个构造函数的情况。</p><h2 id="10-重载和重写的区别"><a href="#10-重载和重写的区别" class="headerlink" title="10. 重载和重写的区别"></a>10. 重载和重写的区别</h2><ul><li><strong>重载:</strong> 发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同,发生在编译时。   </li><li><strong>重写:</strong> 发生在父子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类;如果父类方法访问修饰符为 private 则子类就不能重写该方法。</li></ul><h2 id="11-Java-面向对象编程三大特性-封装-继承-多态"><a href="#11-Java-面向对象编程三大特性-封装-继承-多态" class="headerlink" title="11. Java 面向对象编程三大特性: 封装 继承 多态"></a>11. 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="12-String-StringBuffer-和-StringBuilder-的区别是什么-String-为什么是不可变的"><a href="#12-String-StringBuffer-和-StringBuilder-的区别是什么-String-为什么是不可变的" class="headerlink" title="12. String StringBuffer 和 StringBuilder 的区别是什么? String 为什么是不可变的?"></a>12. String StringBuffer 和 StringBuilder 的区别是什么? String 为什么是不可变的?</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>AbstractStringBuilder.java</p><pre class=" language-java"><code class="language-java"><span class="token keyword">abstract</span> <span class="token keyword">class</span> <span class="token class-name">AbstractStringBuilder</span> <span class="token keyword">implements</span> <span class="token class-name">Appendable</span><span class="token punctuation">,</span> CharSequence <span class="token punctuation">{</span> <span class="token keyword">char</span><span class="token punctuation">[</span><span class="token punctuation">]</span> value<span class="token punctuation">;</span> <span class="token keyword">int</span> count<span class="token punctuation">;</span> <span class="token function">AbstractStringBuilder</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token function">AbstractStringBuilder</span><span class="token punctuation">(</span><span class="token keyword">int</span> capacity<span class="token punctuation">)</span> <span class="token punctuation">{</span> value <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">char</span><span class="token punctuation">[</span>capacity<span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre><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="13-自动装箱与拆箱"><a href="#13-自动装箱与拆箱" class="headerlink" title="13. 自动装箱与拆箱"></a>13. 自动装箱与拆箱</h2><ul><li><strong>装箱</strong>:将基本类型用它们对应的引用类型包装起来;</li><li><strong>拆箱</strong>:将包装类型转换为基本数据类型;</li></ul><h2 id="14-在一个静态方法内调用一个非静态成员为什么是非法的"><a href="#14-在一个静态方法内调用一个非静态成员为什么是非法的" class="headerlink" title="14. 在一个静态方法内调用一个非静态成员为什么是非法的?"></a>14. 在一个静态方法内调用一个非静态成员为什么是非法的?</h2><p>由于静态方法可以不通过对象进行调用,因此在静态方法里,不能调用其他非静态变量,也不可以访问非静态变量成员。</p><h2 id="15-在-Java-中定义一个不做事且没有参数的构造方法的作用"><a href="#15-在-Java-中定义一个不做事且没有参数的构造方法的作用" class="headerlink" title="15. 在 Java 中定义一个不做事且没有参数的构造方法的作用"></a>15. 在 Java 中定义一个不做事且没有参数的构造方法的作用</h2><p>Java 程序在执行子类的构造方法之前,如果没有用 <code>super()</code>来调用父类特定的构造方法,则会调用父类中“没有参数的构造方法”。因此,如果父类中只定义了有参数的构造方法,而在子类的构造方法中又没有用 <code>super()</code>来调用父类中特定的构造方法,则编译时将发生错误,因为 Java 程序在父类中找不到没有参数的构造方法可供执行。解决办法是在父类里加上一个不做事且没有参数的构造方法。 </p><h2 id="16-import-java和javax有什么区别?"><a href="#16-import-java和javax有什么区别?" class="headerlink" title="16. import java和javax有什么区别?"></a>16. import java和javax有什么区别?</h2><p>刚开始的时候 JavaAPI 所必需的包是 java 开头的包,javax 当时只是扩展 API 包来使用。然而随着时间的推移,javax 逐渐地扩展成为 Java API 的组成部分。但是,将扩展从 javax 包移动到 java 包确实太麻烦了,最终会破坏一堆现有的代码。因此,最终决定 javax 包将成为标准API的一部分。</p><p>所以,实际上java和javax没有区别。这都是一个名字。</p><h2 id="17-接口和抽象类的区别是什么?"><a href="#17-接口和抽象类的区别是什么?" class="headerlink" title="17. 接口和抽象类的区别是什么?"></a>17. 接口和抽象类的区别是什么?</h2><ol><li>接口的方法默认是 public,所有方法在接口中不能有实现(Java 8 开始接口方法可以有默认实现),而抽象类可以有非抽象的方法。</li><li>接口中除了static、final变量,不能有其他变量,而抽象类中则不一定。</li><li>一个类可以实现多个接口,但只能实现一个抽象类。接口自己本身可以通过extends关键字扩展多个接口。</li><li>接口方法默认修饰符是public,抽象方法可以有public、protected和default这些修饰符(抽象方法就是为了被重写所以不能使用private关键字修饰!)。 </li><li>从设计层面来说,抽象是对类的抽象,是一种模板设计,而接口是对行为的抽象,是一种行为的规范。</li></ol><p>备注:在JDK8中,接口也可以定义静态方法,可以直接用接口名调用。实现类和实现是不可以调用的。如果同时实现两个接口,接口中定义了一样的默认方法,则必须重写,不然会报错。(详见issue:<a href="https://github.com/Snailclimb/JavaGuide/issues/146" target="_blank" rel="noopener">https://github.com/Snailclimb/JavaGuide/issues/146</a>)</p><h2 id="18-成员变量与局部变量的区别有哪些?"><a href="#18-成员变量与局部变量的区别有哪些?" class="headerlink" title="18. 成员变量与局部变量的区别有哪些?"></a>18. 成员变量与局部变量的区别有哪些?</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="19-创建一个对象用什么运算符-对象实体与对象引用有何不同"><a href="#19-创建一个对象用什么运算符-对象实体与对象引用有何不同" class="headerlink" title="19. 创建一个对象用什么运算符?对象实体与对象引用有何不同?"></a>19. 创建一个对象用什么运算符?对象实体与对象引用有何不同?</h2><p>new运算符,new创建对象实例(对象实例在堆内存中),对象引用指向对象实例(对象引用存放在栈内存中)。一个对象引用可以指向0个或1个对象(一根绳子可以不系气球,也可以系一个气球);一个对象可以有n个引用指向它(可以用n条绳子系住一个气球)。</p><h2 id="20-什么是方法的返回值-返回值在类的方法里的作用是什么"><a href="#20-什么是方法的返回值-返回值在类的方法里的作用是什么" class="headerlink" title="20. 什么是方法的返回值?返回值在类的方法里的作用是什么?"></a>20. 什么是方法的返回值?返回值在类的方法里的作用是什么?</h2><p>方法的返回值是指我们获取到的某个方法体中的代码执行后产生的结果!(前提是该方法可能产生结果)。返回值的作用:接收出结果,使得它可以用于其他的操作!</p><h2 id="21-一个类的构造方法的作用是什么-若一个类没有声明构造方法,该程序能正确执行吗-为什么"><a href="#21-一个类的构造方法的作用是什么-若一个类没有声明构造方法,该程序能正确执行吗-为什么" class="headerlink" title="21. 一个类的构造方法的作用是什么? 若一个类没有声明构造方法,该程序能正确执行吗? 为什么?"></a>21. 一个类的构造方法的作用是什么? 若一个类没有声明构造方法,该程序能正确执行吗? 为什么?</h2><p>主要作用是完成对类对象的初始化工作。可以执行。因为一个类即使没有声明构造方法也会有默认的不带参数的构造方法。</p><h2 id="22-构造方法有哪些特性?"><a href="#22-构造方法有哪些特性?" class="headerlink" title="22. 构造方法有哪些特性?"></a>22. 构造方法有哪些特性?</h2><ol><li>名字与类名相同。</li><li>没有返回值,但不能用void声明构造函数。</li><li>生成类的对象时自动执行,无需调用。</li></ol><h2 id="23-静态方法和实例方法有何不同"><a href="#23-静态方法和实例方法有何不同" class="headerlink" title="23. 静态方法和实例方法有何不同"></a>23. 静态方法和实例方法有何不同</h2><ol><li><p>在外部调用静态方法时,可以使用”类名.方法名”的方式,也可以使用”对象名.方法名”的方式。而实例方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象。 </p></li><li><p>静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),而不允许访问实例成员变量和实例方法;实例方法则无此限制。</p></li></ol><h2 id="24-对象的相等与指向他们的引用相等-两者有什么不同"><a href="#24-对象的相等与指向他们的引用相等-两者有什么不同" class="headerlink" title="24. 对象的相等与指向他们的引用相等,两者有什么不同?"></a>24. 对象的相等与指向他们的引用相等,两者有什么不同?</h2><p>对象的相等,比的是内存中存放的内容是否相等。而引用相等,比较的是他们指向的内存地址是否相等。</p><h2 id="25-在调用子类构造方法之前会先调用父类没有参数的构造方法-其目的是"><a href="#25-在调用子类构造方法之前会先调用父类没有参数的构造方法-其目的是" class="headerlink" title="25. 在调用子类构造方法之前会先调用父类没有参数的构造方法,其目的是?"></a>25. 在调用子类构造方法之前会先调用父类没有参数的构造方法,其目的是?</h2><p>帮助子类做初始化工作。</p><h2 id="26-与-equals-重要"><a href="#26-与-equals-重要" class="headerlink" title="26. == 与 equals(重要)"></a>26. == 与 equals(重要)</h2><p><strong>==</strong> : 它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象(基本数据类型==比较的是值,引用数据类型==比较的是内存地址)。</p><p><strong>equals()</strong> : 它的作用也是判断两个对象是否相等。但它一般有两种使用情况:</p><ul><li>情况1:类没有覆盖 equals() 方法。则通过 equals() 比较该类的两个对象时,等价于通过“==”比较这两个对象。</li><li>情况2:类覆盖了 equals() 方法。一般,我们都覆盖 equals() 方法来比较两个对象的内容是否相等;若它们的内容相等,则返回 true (即,认为这两个对象相等)。</li></ul><p><strong>举个例子:</strong></p><pre class=" language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">test1</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span>String<span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">{</span> String a <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">String</span><span class="token punctuation">(</span><span class="token string">"ab"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// a 为一个引用</span> String b <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">String</span><span class="token punctuation">(</span><span class="token string">"ab"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// b为另一个引用,对象的内容一样</span> String aa <span class="token operator">=</span> <span class="token string">"ab"</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 放在常量池中</span> String bb <span class="token operator">=</span> <span class="token string">"ab"</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">// 从常量池中查找</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>aa <span class="token operator">==</span> bb<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// true</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"aa==bb"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>a <span class="token operator">==</span> b<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// false,非同一对象</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"a==b"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>a<span class="token punctuation">.</span><span class="token function">equals</span><span class="token punctuation">(</span>b<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment" spellcheck="true">// true</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"aEQb"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token number">42</span> <span class="token operator">==</span> <span class="token number">42.0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">// true</span> System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"true"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">}</span></code></pre><p><strong>说明:</strong></p><ul><li>String 中的 equals 方法是被重写过的,因为 object 的 equals 方法是比较的对象的内存地址,而 String 的 equals 方法比较的是对象的值。</li><li>当创建 String 类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个 String 对象。</li></ul><h2 id="27-hashCode-与-equals-重要"><a href="#27-hashCode-与-equals-重要" class="headerlink" title="27. hashCode 与 equals (重要)"></a>27. hashCode 与 equals (重要)</h2><p>面试官可能会问你:“你重写过 hashcode 和 equals 么,为什么重写equals时必须重写hashCode方法?”</p><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>是确定该对象在哈希表中的索引位置。<strong><code>hashCode()</code>在散列表中才有用,在其它情况下没用</strong>。在散列表中hashCode() 的作用是获取对象的散列码,进而确定该对象在散列表中的位置。</p><h3 id="hashCode()与equals()的相关规定"><a href="#hashCode()与equals()的相关规定" class="headerlink" title="hashCode()与equals()的相关规定"></a>hashCode()与equals()的相关规定</h3><ol><li>如果两个对象相等,则hashcode一定也是相同的</li><li>两个对象相等,对两个对象分别调用equals方法都返回true</li><li>两个对象有相同的hashcode值,它们也不一定是相等的</li><li><strong>因此,equals 方法被覆盖过,则 hashCode 方法也必须被覆盖</strong></li><li>hashCode() 的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode(),则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)</li></ol><p>推荐阅读:<a href="https://www.cnblogs.com/skywang12345/p/3324958.html" target="_blank" rel="noopener">Java hashCode() 和 equals()的若干问题解答</a></p><h2 id="28-为什么Java中只有值传递?"><a href="#28-为什么Java中只有值传递?" class="headerlink" title="28. 为什么Java中只有值传递?"></a>28. 为什么Java中只有值传递?</h2><p> <a href="https://github.com/Snailclimb/JavaGuide/blob/master/docs/essential-content-for-interview/MostCommonJavaInterviewQuestions/%E7%AC%AC%E4%B8%80%E5%91%A8%EF%BC%882018-8-7%EF%BC%89.md" target="_blank" rel="noopener">为什么Java中只有值传递?</a></p><h2 id="29-简述线程、程序、进程的基本概念。以及他们之间关系是什么"><a href="#29-简述线程、程序、进程的基本概念。以及他们之间关系是什么" class="headerlink" title="29. 简述线程、程序、进程的基本概念。以及他们之间关系是什么?"></a>29. 简述线程、程序、进程的基本概念。以及他们之间关系是什么?</h2><p><strong>线程</strong>与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。 </p><p><strong>程序</strong>是含有指令和数据的文件,被存储在磁盘或其他的数据存储设备中,也就是说程序是静态的代码。</p><p><strong>进程</strong>是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如CPU时间,内存空间,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。<br>线程是进程划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。从另一角度来说,进程属于操作系统的范畴,主要是同一段时间内,可以同时执行一个以上的程序,而线程则是在同一程序内几乎同时执行一个以上的程序段。</p><h2 id="30-线程有哪些基本状态"><a href="#30-线程有哪些基本状态" class="headerlink" title="30. 线程有哪些基本状态?"></a>30. 线程有哪些基本状态?</h2><p>Java 线程在运行的生命周期中的指定时刻只可能处于下面6种不同状态的其中一个状态(图源《Java 并发编程艺术》4.1.4节)。</p><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/19-1-29/Java%E7%BA%BF%E7%A8%8B%E7%9A%84%E7%8A%B6%E6%80%81.png" alt="Java线程的状态"></p><p>线程在生命周期中并不是固定处于某一个状态而是随着代码的执行在不同状态之间切换。Java 线程状态变迁如下图所示(图源《Java 并发编程艺术》4.1.4节):</p><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/19-1-29/Java%20%E7%BA%BF%E7%A8%8B%E7%8A%B6%E6%80%81%E5%8F%98%E8%BF%81.png" alt="Java线程状态变迁"></p><p>由上图可以看出:</p><p>线程创建之后它将处于 <strong>NEW(新建)</strong> 状态,调用 <code>start()</code> 方法后开始运行,线程这时候处于 <strong>READY(可运行)</strong> 状态。可运行状态的线程获得了 cpu 时间片(timeslice)后就处于 <strong>RUNNING(运行)</strong> 状态。</p><blockquote><p>操作系统隐藏 Java虚拟机(JVM)中的 READY 和 RUNNING 状态,它只能看到 RUNNABLE 状态(图源:<a href="https://howtodoinjava.com/" target="_blank" rel="noopener">HowToDoInJava</a>:<a href="https://howtodoinjava.com/java/multi-threading/java-thread-life-cycle-and-thread-states/" target="_blank" rel="noopener">Java Thread Life Cycle and Thread States</a>),所以 Java 系统一般将这两个状态统称为 <strong>RUNNABLE(运行中)</strong> 状态 。</p></blockquote><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3/RUNNABLE-VS-RUNNING.png" alt="RUNNABLE-VS-RUNNING"></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="31-关于-final-关键字的一些总结"><a href="#31-关于-final-关键字的一些总结" class="headerlink" title="31 关于 final 关键字的一些总结"></a>31 关于 final 关键字的一些总结</h2><p>final关键字主要用在三个地方:变量、方法、类。</p><ol><li>对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。</li><li>当用final修饰一个类时,表明这个类不能被继承。final类中的所有成员方法都会被隐式地指定为final方法。</li><li>使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升(现在的Java版本已经不需要使用final方法进行这些优化了)。类中所有的private方法都隐式地指定为final。</li></ol><h2 id="32-Java-中的异常处理"><a href="#32-Java-中的异常处理" class="headerlink" title="32 Java 中的异常处理"></a>32 Java 中的异常处理</h2><h3 id="Java异常类层次结构图"><a href="#Java异常类层次结构图" class="headerlink" title="Java异常类层次结构图"></a>Java异常类层次结构图</h3><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-2/Exception.png" alt="Java异常类层次结构图"></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>下面这部分内容来自issue:<a href="https://github.com/Snailclimb/JavaGuide/issues/190" target="_blank" rel="noopener">https://github.com/Snailclimb/JavaGuide/issues/190</a>。</p><p><strong>注意:</strong> 当try语句和finally语句中都有return语句时,在方法返回之前,finally语句的内容将被执行,并且finally语句的返回值将会覆盖原始的返回值。如下:</p><pre class=" language-java"><code class="language-java"> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">int</span> <span class="token function">f</span><span class="token punctuation">(</span><span class="token keyword">int</span> value<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> value <span class="token operator">*</span> value<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>value <span class="token operator">==</span> <span class="token number">2</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre><p>如果调用 <code>f(2)</code>,返回值将是0,因为finally语句的返回值覆盖了try语句块的返回值。</p><h2 id="33-Java序列化中如果有些字段不想进行序列化,怎么办?"><a href="#33-Java序列化中如果有些字段不想进行序列化,怎么办?" class="headerlink" title="33 Java序列化中如果有些字段不想进行序列化,怎么办?"></a>33 Java序列化中如果有些字段不想进行序列化,怎么办?</h2><p>对于不想进行序列化的变量,使用transient关键字修饰。</p><p>transient关键字的作用是:阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被transient修饰的变量值不会被持久化和恢复。transient只能修饰变量,不能修饰类和方法。</p><h2 id="34-获取用键盘输入常用的两种方法"><a href="#34-获取用键盘输入常用的两种方法" class="headerlink" title="34 获取用键盘输入常用的两种方法"></a>34 获取用键盘输入常用的两种方法</h2><p>方法1:通过 Scanner</p><pre class=" language-java"><code class="language-java">Scanner input <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Scanner</span><span class="token punctuation">(</span>System<span class="token punctuation">.</span>in<span class="token punctuation">)</span><span class="token punctuation">;</span>String s <span class="token operator">=</span> input<span class="token punctuation">.</span><span class="token function">nextLine</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>input<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><p>方法2:通过 BufferedReader </p><pre class=" language-java"><code class="language-java">BufferedReader input <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">BufferedReader</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">InputStreamReader</span><span class="token punctuation">(</span>System<span class="token punctuation">.</span>in<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> String s <span class="token operator">=</span> input<span class="token punctuation">.</span><span class="token function">readLine</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> </code></pre><h2 id="35-Java-中-IO-流"><a href="#35-Java-中-IO-流" class="headerlink" title="35 Java 中 IO 流"></a>35 Java 中 IO 流</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/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。</li><li>OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。</li></ul><p>按操作方式分类结构图:</p><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/IO-%E6%93%8D%E4%BD%9C%E6%96%B9%E5%BC%8F%E5%88%86%E7%B1%BB.png" alt="IO-操作方式分类"></p><p>按操作对象分类结构图:</p><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/IO-%E6%93%8D%E4%BD%9C%E5%AF%B9%E8%B1%A1%E5%88%86%E7%B1%BB.png" alt="IO-操作对象分类"></p><h3 id="既然有了字节流-为什么还要有字符流"><a href="#既然有了字节流-为什么还要有字符流" class="headerlink" title="既然有了字节流,为什么还要有字符流?"></a>既然有了字节流,为什么还要有字符流?</h3><p>问题本质想问:<strong>不管是文件读写还是网络发送接收,信息的最小存储单元都是字节,那为什么 I/O 流操作要分为字节流操作和字符流操作呢?</strong></p><p>回答:字符流是由 Java 虚拟机将字节转换得到的,问题就出在这个过程还算是非常耗时,并且,如果我们不知道编码类型就很容易出现乱码问题。所以, I/O 流就干脆提供了一个直接操作字符的接口,方便我们平时对字符进行流操作。如果音频文件、图片等媒体文件用字节流比较好,如果涉及到字符的话使用字符流比较好。</p><h3 id="BIO-NIO-AIO-有什么区别"><a href="#BIO-NIO-AIO-有什么区别" class="headerlink" title="BIO,NIO,AIO 有什么区别?"></a>BIO,NIO,AIO 有什么区别?</h3><ul><li><strong>BIO (Blocking I/O):</strong> 同步阻塞I/O模式,数据的读取写入必须阻塞在一个线程内等待其完成。在活动连接数不是特别高(小于单机1000)的情况下,这种模型是比较不错的,可以让每一个连接专注于自己的 I/O 并且编程模型简单,也不用过多考虑系统的过载、限流等问题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效的 I/O 处理模型来应对更高的并发量。</li><li><strong>NIO (New I/O):</strong> NIO是一种同步非阻塞的I/O模型,在Java 1.4 中引入了NIO框架,对应 java.nio 包,提供了 Channel , Selector,Buffer等抽象。NIO中的N可以理解为Non-blocking,不单纯是New。它支持面向缓冲的,基于通道的I/O操作方法。 NIO提供了与传统BIO模型中的 <code>Socket</code> 和 <code>ServerSocket</code> 相对应的 <code>SocketChannel</code> 和 <code>ServerSocketChannel</code> 两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。对于低负载、低并发的应用程序,可以使用同步阻塞I/O来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发</li><li><strong>AIO (Asynchronous I/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><h2 id="36-常见关键字总结-static-final-this-super"><a href="#36-常见关键字总结-static-final-this-super" class="headerlink" title="36. 常见关键字总结:static,final,this,super"></a>36. 常见关键字总结:static,final,this,super</h2><p>详见笔主的这篇文章: <a href="https://gitee.com/SnailClimb/JavaGuide/blob/master/docs/java/Basis/final、static、this、super.md" target="_blank" rel="noopener">https://gitee.com/SnailClimb/JavaGuide/blob/master/docs/java/Basis/final、static、this、super.md</a></p><h2 id="37-Collections-工具类和-Arrays-工具类常见方法总结"><a href="#37-Collections-工具类和-Arrays-工具类常见方法总结" class="headerlink" title="37. Collections 工具类和 Arrays 工具类常见方法总结"></a>37. Collections 工具类和 Arrays 工具类常见方法总结</h2><p>详见笔主的这篇文章: <a href="https://gitee.com/SnailClimb/JavaGuide/blob/master/docs/java/Basis/Arrays,CollectionsCommonMethods.md" target="_blank" rel="noopener">https://gitee.com/SnailClimb/JavaGuide/blob/master/docs/java/Basis/Arrays,CollectionsCommonMethods.md</a></p><h3 id="38-深拷贝-vs-浅拷贝"><a href="#38-深拷贝-vs-浅拷贝" class="headerlink" title="38. 深拷贝 vs 浅拷贝"></a>38. 深拷贝 vs 浅拷贝</h3><ol><li><strong>浅拷贝</strong>:对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝,此为浅拷贝。</li><li><strong>深拷贝</strong>:对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容,此为深拷贝。</li></ol><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-7/java-deep-and-shallow-copy.jpg" alt="deep and shallow copy"></p><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li><a href="https://stackoverflow.com/questions/1906445/what-is-the-difference-between-jdk-and-jre" target="_blank" rel="noopener">https://stackoverflow.com/questions/1906445/what-is-the-difference-between-jdk-and-jre</a></li><li><a href="https://www.educba.com/oracle-vs-openjdk/" target="_blank" rel="noopener">https://www.educba.com/oracle-vs-openjdk/</a></li><li><a href="https://stackoverflow.com/questions/22358071/differences-between-oracle-jdk-and-openjdk?answertab=active#tab-top" target="_blank" rel="noopener">https://stackoverflow.com/questions/22358071/differences-between-oracle-jdk-and-openjdk?answertab=active#tab-top</a></li></ul><h2 id="公众号"><a href="#公众号" class="headerlink" title="公众号"></a>公众号</h2><p>如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。</p><p><strong>《Java面试突击》:</strong> 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本<a href="#公众号">公众号</a>后台回复 <strong>“Java面试突击”</strong> 即可免费领取!</p><p><strong>Java工程师必备学习资源:</strong> 一些Java工程师常用学习资源公众号后台回复关键字 <strong>“1”</strong> 即可免费无套路获取。 </p><p><img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/167598cd2e17b8ec.png" alt="我的公众号"></p>]]></content>
<categories>
<category> Java </category>
</categories>
<tags>
<tag> Java </tag>
<tag> NIO </tag>
<tag> IO </tag>
</tags>
</entry>
</search>
1
https://gitee.com/SnailClimb/SnailClimb.git
git@gitee.com:SnailClimb/SnailClimb.git
SnailClimb
SnailClimb
SnailClimb
master

搜索帮助